Kokil Thapa - Professional Web Developer in Nepal
Freelancer Web Developer in Nepal with 15+ Years of Experience

Kokil Thapa is an experienced full-stack web developer focused on building fast, secure, and scalable web applications. He helps businesses and individuals create SEO-friendly, user-focused digital platforms designed for long-term growth.

Manage Multi-Cloud State with Terraform

By Kokil Thapa | Last reviewed: August 2026

If you are running infrastructure across AWS, Azure, and GCP simultaneously, local state files are a liability waiting to cause an outage. To safely manage multi-cloud state with Terraform, you must centralize state storage in a remote backend with strict locking, isolate environments via workspaces or directory structures, and automate all changes through CI/CD. This approach prevents drift, eliminates race conditions between engineers, and ensures your Nepal-based team can collaborate on global infrastructure without stepping on each other's toes.

How do you configure remote backends to manage multi-cloud state with Terraform?

The foundation of any production-grade infrastructure is the remote backend. When I set up cloud hosting services in Nepal or international multi-region deployments, the first step is always moving state off local disks. In a multi-cloud context, you face a chicken-and-egg problem: where do you store the state that manages the clouds themselves?

The industry-standard pattern in 2026 is to pick one "primary" cloud for state storage, or use a dedicated third-party service like HashiCorp Cloud Platform (HCP) Terraform. For most teams I work with, we select the cloud provider hosting the majority of the workload as the state home. If you are hybrid, S3 with DynamoDB locking remains the most battle-tested option due to its strong consistency model and granular IAM policies.

S3 Backend Configuration with Locking

Your backend configuration must be identical across all environments. A common mistake is enabling versioning but forgetting server-side encryption or public access blocking. Here is a production-ready configuration for Terraform 1.9+:

<pre><code>terraform {
  required_version = ">= 1.9.0"

  backend "s3" {
    bucket         = "my-org-terraform-state-prod"
    key            = "multi-cloud/global/terraform.tfstate"
    region         = "us-east-1"
    encrypt        = true
    dynamodb_table = "terraform-state-lock"
    
    # Critical security settings
    skip_metadata_api_check = false
    sts_region             = "us-east-1"
    
    # Prevent accidental deletion
    workspace_key_prefix = "env"
  }
}
</code></pre>

The dynamodb_table is non-negotiable. Without it, two engineers applying changes simultaneously will corrupt your state file. The table requires only a primary key named LockID of type String. On a recent legal-tech portal project, we recovered from a near-miss corruption event solely because DynamoDB locking caught a concurrent apply during a deployment window.

Cross-Region and Cross-Account Replication

For disaster recovery, enable S3 Cross-Region Replication (CRR) on your state bucket. State files are small but irreplaceable; losing them means importing every resource manually. Configure replication to a different AWS region with its own KMS key. This adds roughly Rs 500–1,000 (~USD 4–8) per month to your bill but provides insurance against regional outages affecting your ability to manage infrastructure.

Multi-Cloud State ArchitectureTerraform CLI / CI RunnerPlan & Apply OperationsAWS + Azure + GCP ProvidersS3 State BucketEncrypted .tfstate FilesVersioning EnabledDynamoDB Lock TableLockID Primary KeyPrevents Concurrent WritesDR Replica BucketCross-Region ReplicationSeparate KMS KeyAsync Replication
Terraform multi-cloud state architecture showing centralized S3 backend with DynamoDB locking and cross-region disaster recovery replication

What is the best strategy for isolating state across multiple cloud providers?

When you manage multi-cloud state with Terraform, isolation determines your blast radius. There are three viable strategies, and choosing wrong leads to either operational paralysis or catastrophic coupling.

StrategyBest ForRisk LevelComplexity
Single State FileTightly coupled resources, small teamsHigh (one error breaks all clouds)Low
Directory-Based IsolationProduction multi-cloud, compliance boundariesLow (independent state per cloud/env)Medium
WorkspacesIdentical environments (dev/staging/prod parity)Medium (shared config, separate state)Low

For most multi-cloud projects I've shipped, including e-commerce platforms spanning AWS compute and Azure AI services, directory-based isolation wins. Each cloud provider gets its own root module with its own backend key:

  • infrastructure/aws/ → backend key: multi-cloud/aws/terraform.tfstate
  • infrastructure/azure/ → backend key: multi-cloud/azure/terraform.tfstate
  • infrastructure/gcp/ → backend key: multi-cloud/gcp/terraform.tfstate
  • infrastructure/shared/ → DNS, monitoring, cross-cloud networking

This structure means a failed Azure apply never touches AWS state. It also allows parallel CI/CD pipelines — your team can deploy network changes in GCP while another engineer updates EC2 instances in AWS without waiting. The tradeoff is managing cross-stack references, which brings us to the next section.

When Workspaces Make Sense

Workspaces shine when your multi-cloud topology is identical across environments. If dev, staging, and prod all have the same AWS+Azure shape, workspaces avoid code duplication. But never use workspaces to separate cloud providers within the same environment — that couples their lifecycles and defeats the purpose of multi-cloud resilience. As noted in discussions about migration strategies, coupling unrelated concerns creates technical debt that compounds over time.

How do you handle cross-cloud dependencies and data sharing safely?

The hardest part of multi-cloud Terraform isn't provisioning resources — it's making them talk to each other without creating circular dependencies or fragile import chains. When building systems like those described in real-time feature architectures, cross-service data flow must be explicit and versioned.

Using terraform_remote_state Correctly

The terraform_remote_state data source reads outputs from another state file. Use it sparingly and only for stable, infrequently-changing values like VPC IDs, subnet CIDRs, or shared service endpoints:

<pre><code>data "terraform_remote_state" "aws_network" {
  backend = "s3"
  config = {
    bucket = "my-org-terraform-state-prod"
    key    = "multi-cloud/aws/network/terraform.tfstate"
    region = "us-east-1"
  }
}

resource "azurerm_virtual_network_peering" "aws_to_azure" {
  name                = "aws-peer"
  resource_group_name = azurerm_resource_group.main.name
  virtual_network_name = azurerm_virtual_network.main.name
  
  remote_virtual_network_id = data.terraform_remote_state.aws_network.outputs.vpc_id
  
  # Explicit dependency declaration
  depends_on = [data.terraform_remote_state.aws_network]
}
</code></pre>

Critical rule: Never expose sensitive values through remote state outputs. Database passwords, API keys, and certificates should flow through secret managers (AWS Secrets Manager, Azure Key Vault), not Terraform state. Remote state is readable by anyone with bucket access; treat it as public metadata.

Terragrunt for Dependency Management

For complex multi-cloud setups, vanilla Terraform's manual dependency management becomes unmanageable. Terragrunt adds a dependency graph layer that automatically applies upstream stacks before downstream ones. On a recent project involving AWS EKS, Azure PostgreSQL, and GCP Cloud SQL, Terragrunt reduced our deployment coordination overhead from hours to minutes. The dependency block replaces fragile terraform_remote_state boilerplate with declarative ordering.

Cross-Cloud Dependency Resolution Order1. Shared NetworkingVPCs, VNets, Transit GWNo External Dependencies2. Cloud-Specific InfraEKS, AKS, GKE ClustersDepends on Networking3. Application LayerDNS, LBs, Service MeshDepends on ClustersData Flow: Outputs → Remote State / Secret Manager✓ VPC IDs, Subnet CIDRs, Endpoint URLs via remote_state✗ Passwords, Keys, Tokens via Secrets Manager ONLY⚠ Never pass secrets through Terraform outputs
Cross-cloud dependency resolution order showing layered application of networking, infrastructure, and application stacks with secure data flow boundaries

How do you integrate Terraform state management into CI/CD pipelines?

Manual terraform apply from developer laptops is unacceptable for multi-cloud state. Every change must flow through version control and automated pipelines. This is especially true when coordinating with teams across time zones or managing client projects where audit trails matter for compliance.

GitLab CI Pipeline Structure

On projects using Deployer-style workflows similar to those I maintain for sister sites on shared EC2 infrastructure, the CI pipeline enforces plan-review-apply gates:

  1. Validate Stage: terraform fmt -check, terraform validate, tflint, checkov security scanning
  2. Plan Stage: Generate plan output, save to artifact, post summary to merge request comment
  3. Approval Gate: Manual approval required for production plans; auto-approve for dev/staging if tests pass
  4. Apply Stage: Apply approved plan, capture output, update documentation
<pre><code># .gitlab-ci.yml excerpt for multi-cloud state safety
stages:
  - validate
  - plan
  - approve
  - apply

.terraform_base:
  image: hashicorp/terraform:1.9.4
  cache:
    key: "$CI_COMMIT_REF_SLUG-tf"
    paths:
      - .terraform/

plan_aws:
  extends: .terraform_base
  stage: plan
  script:
    - cd infrastructure/aws
    - terraform init -backend-config=backend-prod.hcl
    - terraform plan -out=tfplan -var-file=prod.tfvars
    - terraform show -json tfplan > plan.json
  artifacts:
    paths:
      - infrastructure/aws/tfplan
      - infrastructure/aws/plan.json
    expire_in: 7 days

apply_aws:
  extends: .terraform_base
  stage: apply
  needs: ["plan_aws"]
  when: manual  # Explicit approval gate
  script:
    - cd infrastructure/aws
    - terraform init -backend-config=backend-prod.hcl
    - terraform apply tfplan
</code></pre>

State Locking in CI Environments

CI runners can fail mid-apply, leaving locks orphaned. Always implement lock cleanup in your pipeline's after_script or failure handler. For GitLab CI, add a cleanup job that runs terraform force-unlock LOCK_ID only when the apply job fails unexpectedly. Better yet, use short-lived credentials via OIDC federation so compromised runner tokens can't permanently lock state.

What security controls protect multi-cloud Terraform state in 2026?

Your state file contains everything an attacker needs to own your infrastructure: resource IDs, IP addresses, sometimes even plaintext secrets if someone made a mistake. Securing state is as important as securing the infrastructure itself. Teams exploring cybersecurity trends in 2026 consistently find that IaC state stores are under-protected attack surfaces.

Encryption and Access Control Checklist

  • Server-side encryption: AES-256 minimum; customer-managed KMS keys preferred over AWS-managed
  • Bucket policy: Deny all unencrypted uploads (s3:x-amz-server-side-encryption condition)
  • IAM least privilege: Separate roles for plan (read-only) vs apply (write); no wildcard permissions
  • MFA delete: Enable on state buckets to prevent accidental or malicious deletion
  • Access logging: S3 access logs to a separate account; CloudTrail for API-level audit
  • Network isolation: VPC endpoints for S3/DynamoDB; no public internet access to state backend

Sensitive Variable Handling

Never store secrets in .tfvars files committed to Git. Use environment variables injected by CI, or better yet, reference secret manager ARNs directly in HCL:

<pre><code>variable "db_password_arn" {
  description = "ARN of database password in Secrets Manager"
  type        = string
}

data "aws_secretsmanager_secret_version" "db_password" {
  secret_id = var.db_password_arn
}

resource "aws_db_instance" "main" {
  password = data.aws_secretsmanager_secret_version.db_password.secret_string
  
  # Mark as sensitive to prevent logging
  lifecycle {
    ignore_changes = [password]
  }
}
</code></pre>

This pattern keeps secrets out of state entirely. The password is fetched at apply time and never persisted in .tfstate. Combined with ignore_changes, it prevents Terraform from trying to reconcile drift on rotated credentials.

State Security Defense LayersLayer 1: Encryption at Rest & TransitAES-256 KMS • TLS 1.3 • Bucket Policy Deny UnencryptedLayer 2: Identity & Access ControlOIDC Federation • Plan/Apply Role Separation • MFA Delete • Short-Lived TokensLayer 3: Network IsolationVPC Endpoints • PrivateLink • No Public Internet • Security Group WhitelistingLayer 4: Secret ExternalizationSecrets Manager References • Zero Plaintext in State • Rotation-Aware Lifecycle Rules
Four-layer security model for protecting Terraform multi-cloud state: encryption, identity, network isolation, and secret externalization

Implementing Resilient Multi-Cloud State Management

To successfully manage multi-cloud state with Terraform in 2026, start with a remote backend that has locking and encryption enabled, isolate state by cloud provider using directory structures, automate every change through CI/CD with explicit approval gates, and treat state security as a first-class concern equal to application security. These patterns have proven reliable across production deployments ranging from Nepal-based legal-tech portals to international e-commerce platforms spanning three cloud regions.

If your team is struggling with state corruption, slow multi-cloud deployments, or unclear ownership boundaries across providers, the issue is almost always architectural rather than tooling. Review your backend configuration, audit your dependency graph, and ensure your CI pipeline enforces the discipline that humans inevitably relax under pressure. For hands-on assistance designing or rescuing a multi-cloud Terraform setup, reach out to discuss your infrastructure challenges.

Frequently Asked Questions

Use cloud-native object storage with versioning enabled, such as AWS S3, Azure Blob Storage, or GCP Cloud Storage. Never store multi-cloud state locally or in Git. Remote backends provide locking, encryption at rest, and audit trails required for production infrastructure spanning multiple providers.

Technically yes, but practically dangerous. A single state file creates a blast radius where one failed apply locks all clouds. In my experience deploying infrastructure for clients, splitting state by cloud provider and environment prevents cross-cloud dependency failures and reduces lock contention during parallel team workflows.

Each backend requires its own locking mechanism. AWS S3 uses DynamoDB tables, Azure Blob uses native lease locking, and GCP Cloud Storage uses built-in consistency. You cannot share a single lock table across providers. Define separate backend blocks per cloud in your Terraform configuration to ensure safe concurrent operations.

Negligible for most projects. AWS S3 Standard costs roughly USD 0.023 per GB monthly (NPR 3), plus DynamoDB read/write units for locking. Azure Blob and GCP Cloud Storage have similar pricing tiers. For typical multi-cloud setups under 100MB state, expect less than USD 1 (NPR 135) monthly total including API requests and versioning.

Native Terraform suffices for most multi-cloud setups using workspaces or directory-based separation. Terragrunt adds value only when managing dozens of environments with repetitive backend configurations. On client projects I have maintained, native Terraform with standardized backend templates reduced complexity without sacrificing DRY principles across AWS and Azure deployments.

Enable server-side encryption on every backend. AWS S3 supports SSE-KMS with customer-managed keys, Azure Blob integrates with Key Vault, and GCP uses Cloud KMS. Always encrypt state files because they contain sensitive resource IDs, connection strings, and sometimes plaintext secrets. Verify encryption status via cloud console audits regularly.

Terraform fails immediately without corrupting state. The operation is atomic; partial writes do not occur. Configure backend health checks and use multi-region replication for critical state stores. In production systems I have managed, S3 Cross-Region Replication or Azure Geo-Redundant Storage provides disaster recovery without manual intervention during regional outages.

Run terraform init -migrate-state after configuring the new backend block. Terraform prompts confirmation before uploading. Back up local terraform.tfstate first. For multi-cloud migrations, move each provider's state separately to avoid cross-dependency issues. Test migration in staging before production. Validate resource counts match post-migration using terraform plan to detect drift.

Yes, remote backends enforce locking automatically. Only one write operation proceeds at a time; others queue or fail gracefully. Read operations like plan remain unrestricted. Implement IAM policies granting least-privilege access per cloud backend. On teams I have worked with, separate state files per cloud reduced lock wait times significantly compared to monolithic state approaches.

Organize by provider then environment: aws/prod/, azure/staging/, gcp/dev/. Each directory contains its own backend.tf and main configuration. Shared modules live in a separate modules/ directory referenced via relative paths. This structure isolates state files, simplifies CI/CD pipelines, and allows independent deployment cycles per cloud without risking unintended cross-provider changes.

Sharing credentials across cloud backends, disabling versioning, ignoring state file size growth, and hardcoding backend configs instead of using variables. Another frequent issue is forgetting to update backend settings after cloud account restructuring. Always validate backend connectivity before applying and monitor state file versions for unexpected changes indicating unauthorized modifications or automation failures.

Terraform Cloud offers unified state management, policy enforcement, and private module registry across clouds without configuring individual backends. Pricing starts at USD 70 per user monthly (NPR 9,400). Self-managed backends cost less but require operational overhead for encryption, locking, and access control. Choose Terraform Cloud for teams needing governance; choose self-managed for budget-sensitive projects with strong DevOps capability.

Enable versioning on object storage backends to retain historical states automatically. Supplement with scheduled snapshots using cloud-native tools like AWS Backup or Azure Recovery Services. Export state periodically via terraform state pull to encrypted offline storage. Test restoration quarterly. On legal-tech platforms I maintain, versioned S3 buckets with lifecycle policies balance cost and recovery point objectives effectively.

Grant read-write access only to specific backend buckets or containers, never account-wide storage permissions. Use short-lived credentials via OIDC federation instead of static keys. Restrict delete permissions to prevent accidental state removal. Separate pipeline identities per cloud provider. Audit access logs monthly. Least-privilege access prevents compromised CI runners from destroying infrastructure state across your entire multi-cloud estate.

First, restore from versioned backend history using terraform state push with a known-good snapshot. Compare corrupted versus restored state using diff tools. Check cloud audit logs for unauthorized API calls. Validate backend configuration matches expected encryption and locking settings. If corruption persists, manually reconcile resources using terraform import. Document root cause to prevent recurrence through improved access controls or automation safeguards.

Share this article

Quick Contact Options
Choose how you want to connect me: