How to Share Terraform State

Terraform state sharing centralizes infrastructure deployment data for team collaboration. It enables secure sharing of outputs between configurations, streamlining interconnected infrastructure management.

Terraform state is how your code understands the infrastructure it controls, acting as a record of deployed resources. When multiple teams or configurations need to interact, sharing this state effectively becomes a core requirement for collaboration. This article will explain Terraform state and detail the methods for secure sharing across different deployments.

What is Terraform State?

At its core, Terraform state is a snapshot of your infrastructure. When you run terraform apply, Terraform records the mapping between your configuration and the real-world resources it created. This state file, typically named terraform.tfstate, is essential for Terraform to:

  • Track resources: Know which resources it manages.
  • Plan changes: Understand the current state of your infrastructure to determine what needs to be created, updated, or destroyed.
  • Manage metadata: Store resource attributes and dependencies.

Without a valid state file, Terraform would have no idea what it's managing, leading to potential resource duplication or accidental destruction, commonly know as drift.

What are Dependencies Between Terraform State Files?

While a single Terraform configuration often generates a single state file, real-world deployments rarely live in isolation. Often, one set of Terraform state files depends on another. For example:

  • A networking team might deploy a VPC and subnets, with the details stored in a Terraform state file.
  • An application team might then deploy EC2 instances into those subnets, the detaisl stored in a different Terraform state file.
  • A database team might deploy an RDS instance, and the application team needs its endpoint, which the details of it are stored in the state file.

In these scenarios, the output of one Terraform deployment (e.g., VPC ID, subnet IDs, database endpoint) becomes an input for another. This is where dependencies between Terraform state files emerge. Without a mechanism to share these outputs, each team would have to manually discover and input these values, which is error-prone and defeats the purpose of IaC.

To share outputs frome one workspace to another, the output must be defined in the Terraform code itself:

# Define a resource, for example, an AWS S3 bucket
resource "aws_s3_bucket" "my_bucket" {
  bucket = "my-example-bucket-12345"
  acl    = "private"

  tags = {
    Environment = "Dev"
    Project     = "TerraformExamples"
  }
}

# Define an output for the S3 bucket's ID
output "bucket_id" {
  description = "The ID of the S3 bucket"
  value       = aws_s3_bucket.my_bucket.id
}

Use Cases: When to Share State Between Terraform Deployments

Sharing Terraform state becomes invaluable in several common scenarios:

Modular Infrastructure: When you break down your infrastructure into logical, independent Terrafor, modules (e.g., network module, compute module, database module). Each module and deployment of it manages its state, but outputs from one are consumed by others.

Multi-Team Environments: As illustrated above, different teams own different pieces of the infrastructure, but their components need to interact. Such as network team deploys all network related infrastructure, but application teams can read the Terraform state files from the network deployments.

Environments (Dev, Staging, Prod): While each environment typically has its own distinct state, there might be shared foundational elements (e.g., a core shared services VPC) whose state needs to be accessible across environments.

Centralized Data: If you have common data points like a shared S3 bucket name or an IAM role ARN that multiple deployments need to reference.

Terraform State: Security Best Practices

Sharing Terraform state, while powerful, introduces security considerations that must be addressed:

Sensitive Data: Terraform state can contain sensitive information like database passwords, API keys, or private IP addresses.

  • Never commit terraform.tfstate files directly to version control.
  • Utilize remote backends (like S3, Azure Blob Storage, GCS) that encrypt state at rest.
  • Employ secret management tools (e.g., AWS Secrets Manager, Azure Key Vault, HashiCorp Vault) to store and retrieve sensitive values, referencing them in your Terraform configuration rather than hardcoding them or storing them in plain text in the state file. Terraform's data "aws_secretsmanager_secret" or similar data sources are your friends here.

Access Control (Least Privilege): Not everyone needs access to all state files.

  • Implement strict IAM policies (AWS), RBAC (Azure/GCP), or workspace permissions (Terraform Cloud/Scalr) to control who can read, write, and modify state.
  • Differentiate read-only access, for consuming outputs, from write access, for managing resources.

State Locking: Concurrent operations on the same state file can lead to corruption.

  • Ensure your remote backend supports state locking to prevent multiple users or processes from writing to the state simultaneously. Most cloud storage backends and dedicated Terraform platforms, like Terraform Cloud or Scalr offer state locking.

State History and Rollback:

  • Leverage remote backends that provide versioning for your state files. This allows you to revert to a previous working state if a deployment goes wrong.

Auditing:

  • Enable logging and auditing for your state backend to track who accessed and modified the state files. Again, this is where dedicated Terraform platforms really help.

How to Share State When using Terraform

When you're operating purely with open-source Terraform, without a managed platform like Terraform Cloud or Scalr, state management and sharing become your responsibility. The core mechanism for sharing state is through remote backends. Terraform supports various remote backends, primarily cloud storage services, such as S3, which allow you to store your terraform.tfstate file in a centralized, accessible location.

This approach requires you to manually configure the backend and often set up supporting services for state locking.

Configuring a Remote Backend (e.g., S3, Azure Blob Storage, GCS)

The most common and recommended way to share state in an open-source setup is by using cloud object storage. These services offer high durability, versioning, and often built-in locking mechanisms.

General Steps:

  1. Choose a Backend: Select a cloud storage service like Amazon S3, Azure Blob Storage, or Google Cloud Storage.
  2. Create the Storage Resource: Manually create the necessary bucket/container in your chosen cloud provider before running terraform init.
  3. Configure Terraform Backend: Add a backend block to your root module's main.tf (or a dedicated backend.tf file).

1. Set the Backend in the Code

AWS S3:

Prerequisites:

      • An S3 bucket named my-terraform-state-bucket (with versioning enabled for recovery).
      • A DynamoDB table named terraform-lock-table with a primary key of LockID (string type). This table is used by Terraform for robust state locking.
      • Appropriate IAM permissions for the users/roles running Terraform to access the S3 bucket and DynamoDB table.

Example code snippet:

terraform {
  backend "s3" {
    bucket         = "my-terraform-state-bucket"
    key            = "path/to/my/terraform.tfstate"
    region         = "us-east-1"
    encrypt        = true # Enable server-side encryption
    dynamodb_table = "terraform-lock-table" # For state locking
  }
}

Azure Blob Storage Backend

Prerequisites:

      • An Azure Storage Account (myazuretfstateaccount) and a container (tfstate) created within it.
      • Appropriate Azure RBAC permissions for the identity running Terraform (e.g., "Storage Blob Data Contributor" role on the container). Azure Blob Storage provides its own locking mechanism.

Example code snippet:

terraform {
  backend "azurerm" {
    resource_group_name  = "tfstate-rg"
    storage_account_name = "myazuretfstateaccount"
    container_name       = "tfstate"
    key                  = "path/to/my/terraform.tfstate"
    # Authentication is typically handled via Azure CLI login, Managed Identities, or Service Principals
  }
}

Google Cloud Storage (GCS) Backend

Prerequisites:

      • A GCS bucket named my-gcs-terraform-state-bucket (with object versioning enabled).
      • Appropriate IAM permissions for the identity running Terraform (e.g., "Storage Object Admin" on the bucket). GCS provides native state locking.

Example code snippet:

terraform {
  backend "gcs" {
    bucket = "my-gcs-terraform-state-bucket"
    prefix = "path/to/my/states" # Optional: A prefix to organize state files within the bucket
  }
}

2. Initializing Terraform with Remote Backend

After defining the backend block, run terraform init. Terraform will detect the backend configuration and prompt you to migrate any existing local state to the remote backend.

terraform init
Initializing the backend...

Initializing provider plugins...
- Reusing previous version of hashicorp/aws from the dependency lock file
- Using previously-installed hashicorp/aws v5.58.0

Terraform has been successfully initialized!

You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.

If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.

Once the Terraform plan and apply are executed then the state is stored in the backend with outputs defined.

3. Sharing Outputs with terraform_remote_state

Once your state is stored in a remote backend, other Terraform configurations (in different directories or even different Git repositories) can access its outputs using the terraform_remote_state data source. A terraform_remote_state data source is a mechanism that allows one Terraform configuration to read the outputs from another, separately managed, Terraform configuration's state.

Essentially, it's how you tell Terraform, "Go look at the state file for that other deployment, and tell me the values of its declared outputs."

Example of using the remote state data source:

# In a separate Terraform configuration, needing outputs from the 'network' state

data "terraform_remote_state" "network" {
  backend = "s3" # Must match the backend type of the remote state
  config = {
    bucket = "my-terraform-state-bucket"
    key    = "path/to/my/network-terraform.tfstate" # The specific key for the network state
    region = "us-east-1"
    # Include any other backend-specific configuration, e.g., dynamodb_table for S3
    dynamodb_table = "terraform-lock-table"
  }
}

resource "aws_instance" "app_server" {
  ami           = "ami-0abcdef1234567890"
  instance_type = "t2.micro"
  subnet_id     = data.terraform_remote_state.network.outputs.private_subnet_id # Accessing an output
  # ...
}

The key to the above is that the remote state snippet has the proper keys and permissions to read the other state files in the bucket.

Important Considerations for Terraform State Sharing:

  • Manual Backend Management: You are responsible for creating, configuring, and managing the lifecycle of your remote backend (S3 buckets, DynamoDB tables, etc.).
  • State Locking: This is CRITICAL for team collaboration. Ensure your chosen backend supports state locking, and configure it correctly (e.g., DynamoDB with S3). Without it, concurrent terraform apply operations can corrupt your state file.
  • Permissions: Carefully manage IAM/RBAC permissions to your backend storage. Use the principle of least privilege.
  • Versioning: Always enable versioning on your state storage (e.g., S3 Bucket Versioning, GCS Object Versioning) to protect against accidental deletions or corruptions. This allows you to revert to a previous state file if necessary.
  • Encryption: Configure encryption at rest for your state files in the cloud storage bucket. Most cloud providers offer this by default or through easy configuration.
  • CI/CD Integration: When using remote backends, your CI/CD pipelines will simply need the appropriate cloud credentials to access the backend storage.
  • Workspace Management: While Terraform has built-in terraform workspace commands, when using simple remote backends, each workspace typically corresponds to a distinct state file key within your bucket (e.g., path/to/my/states/dev.tfstate, path/to/my/states/prod.tfstate). This is managed by Terraform automatically when you use terraform workspace new <name>.

By carefully planning and implementing these open-source backend strategies, you can achieve robust and secure Terraform state sharing for your team's collaborative infrastructure deployments.

Orchestrating Terraform Runs and State Sharing

While sharing Terraform state files via a remote backend offers a foundational level of collaboration, the true power comes when this capability is integrated into an automated workflow. The ability to programmatically consume outputs from a source state file in a dependent deployment, coupled with a CI/CD pipeline that orchestrates these operations, unlocks highly efficient and reliable infrastructure management. Imagine a scenario where a successful terraform apply for your network infrastructure automatically triggers the deployment of your compute resources, seamlessly inheriting the necessary VPC IDs or subnet details from the network state. This automated dependency management minimizes manual intervention, reduces human error, and ensures that your interconnected infrastructure components are always deployed and updated in the correct sequence, reflecting a single source of truth for your entire environment.

Example

The next question that pops up is how do I actually build this orchestration? There are many tools like Scalr and Terraform Cloud that do thsi out of the box for you, but a Github action is a simple example of doing this.

This example assumes your Terraform state is stored in an S3 bucket with DynamoDB locking, and you're using terraform_remote_state data sources.

Repo 1: network-infra

    • Creates VPC, subnets, security groups.
    • Outputs vpc_id and public_subnet_id.
    • Terraform state stored in s3://my-tf-states/prod/network/terraform.tfstate.
  • Repo 2: app-servers
    • Creates EC2 instances, ALB.
    • Uses terraform_remote_state to get vpc_id and public_subnet_id from network-infra.
    • Terraform state stored in s3://my-tf-states/prod/app-servers/terraform.tfstate.

network-infra GitHub Action (.github/workflows/deploy-network.yml):

YAML

name: Deploy Network Infrastructure

on:
  push:
    branches:
      - main
    paths:
      - 'network-infra/**' # Trigger only if changes are in network-infra folder

env:
  AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
  AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
  AWS_REGION: us-east-1 # Or read from env/config

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Setup Terraform
        uses: hashicorp/setup-terraform@v3
        with:
          terraform_version: 1.x.x # Specify your desired version

      - name: Terraform Init
        id: init
        run: terraform init -backend-config="bucket=my-tf-states" -backend-config="key=prod/network/terraform.tfstate" -backend-config="region=${{ env.AWS_REGION }}" -backend-config="dynamodb_table=terraform-locks"
        working-directory: network-infra

      - name: Terraform Plan
        id: plan
        run: terraform plan -no-color
        working-directory: network-infra
        env:
          TF_VAR_environment: production # Example of passing a variable

      - name: Terraform Apply
        id: apply
        run: terraform apply -auto-approve
        working-directory: network-infra

      # Important: Trigger downstream deployment
      - name: Trigger App Servers Deployment
        if: success() # Only trigger if the network apply was successful
        uses: peter-evans/repository-dispatch@v3
        with:
          token: ${{ secrets.PAT_TOKEN }} # Needs a PAT with repo scope to trigger
          repository: your-org/app-servers # The repository containing app-servers
          event-type: deploy-app-servers # A custom event type for the app-servers workflow
          client-payload: '{"ref": "${{ github.ref }}", "sha": "${{ github.sha }}"}' # Optional payload

app-servers GitHub Action (.github/workflows/deploy-app-servers.yml):

YAML

name: Deploy Application Servers

on:
  push:
    branches:
      - main
    paths:
      - 'app-servers/**'
  repository_dispatch: # This is how the network-infra workflow triggers this one
    types: [deploy-app-servers]

env:
  AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
  AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
  AWS_REGION: us-east-1

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Setup Terraform
        uses: hashicorp/setup-terraform@v3
        with:
          terraform_version: 1.x.x

      - name: Terraform Init
        id: init
        run: terraform init -backend-config="bucket=my-tf-states" -backend-config="key=prod/app-servers/terraform.tfstate" -backend-config="region=${{ env.AWS_REGION }}" -backend-config="dynamodb_table=terraform-locks"
        working-directory: app-servers

      - name: Terraform Plan
        id: plan
        run: terraform plan -no-color
        working-directory: app-servers
        env:
          TF_VAR_environment: production # Example of passing a variable

      - name: Terraform Apply
        id: apply
        run: terraform apply -auto-approve
        working-directory: app-servers

While the above gets the job done, there is quite a bit of connecting and maintenance that needs to be done to make sure this keeps running. This is where using something like Scalr's federated environments, run triggers, and state sharing helps.

Conclusion

Terraform state is the cornerstone of effective infrastructure management with Terraform. Understanding its role, the dependencies between state files, and the various methods for secure sharing is paramount for collaborative and scalable IaC deployments. By embracing remote backends, implementing strong access controls, and leveraging platforms like Terraform Cloud or Scalr, you can ensure your Terraform state is not just managed, but managed securely and efficiently, empowering your teams to build and deploy infrastructure with confidence.

Extra Resources:

Terraform Workspaces Explained
Compare workspaces across Terraform CLI, Terraform Cloud and Scalr. Quickly grasp differences in state, variables, policy control and choose the right tool.
Terraform State Management: How to manage state in a backend
Master Terraform state management by moving beyond local files. Learn to configure remote backends like S3 or Scalr to securely store state, prevent conflicts with locking, and collaborate safely. This guide covers the essential commands and best practices for production-grade infrastructure.