
Terraform state is how your code knows about the infrastructure it controls. It's a record of what you've deployed. Once multiple teams or configurations need to talk to each other, sharing that state well becomes a core requirement for collaboration. This article explains Terraform state, how to share it securely across deployments, and how managed platforms like Scalr and Terraform Cloud make it easier.
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:
Without a valid state file, Terraform has no idea what it's managing, leading to potential resource duplication or accidental destruction, commonly known as drift.
A single Terraform configuration usually generates a single state file, but real deployments rarely live in isolation. One set of state files often depends on another. For example:
In each of these cases, the output of one deployment (a VPC ID, subnet IDs, a database endpoint) becomes an input for another. Without a way to share those outputs, every team has to go find and type in the values by hand, which is error-prone and defeats the point of IaC.
Sharing Terraform state pays off in a few common situations:
Sharing state is useful, but it comes with a few things you need to watch:
Sensitive data. Terraform state can contain sensitive information like database passwords, API keys, or private IP addresses.
terraform.tfstate files directly to version control.Access control (least privilege). Not everyone needs access to all state files.
State locking. Concurrent operations on the same state file can corrupt it.
State history and rollback. Use remote backends that provide versioning for 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 state files. Dedicated Terraform platforms make this much easier.
With open-source Terraform alone, no managed platform, managing and sharing state is on you. The main building block is the remote backend: cloud object storage that holds your terraform.tfstate file in one central, reachable spot.
You'll configure the backend yourself, and you often have to set up extra services for state locking.
The most common approach is cloud object storage, which offers high durability, versioning, and often built-in locking.
General steps:
terraform init.backend block to your root module.AWS S3. Prerequisites:
LockID (string) for state locking.terraform {
backend "s3" {
bucket = "my-terraform-state-bucket"
key = "path/to/my/terraform.tfstate"
region = "us-east-1"
encrypt = true
dynamodb_table = "terraform-lock-table"
}
}Azure Blob Storage. Prerequisites: storage account and container, and the "Storage Blob Data Contributor" role on the identity running Terraform. Azure Blob Storage provides its own locking mechanism.
terraform {
backend "azurerm" {
resource_group_name = "tfstate-rg"
storage_account_name = "myazuretfstateaccount"
container_name = "tfstate"
key = "path/to/my/terraform.tfstate"
}
}Google Cloud Storage. Prerequisites: a GCS bucket with object versioning enabled, and the "Storage Object Admin" role on the identity running Terraform. GCS provides native state locking.
terraform {
backend "gcs" {
bucket = "my-gcs-terraform-state-bucket"
prefix = "path/to/my/states"
}
}After defining the backend block, run terraform init. Terraform detects the backend configuration and prompts you to migrate any existing local state.
terraform_remote_stateOnce your state is in a remote backend, other configurations (in different directories or even different Git repos) can read its outputs with the terraform_remote_state data source. For more on the output block itself (arguments, for expressions, sensitive flags, and child-module outputs), see Terraform Outputs: How to with Examples.
data "terraform_remote_state" "network" {
backend = "s3"
config = {
bucket = "my-terraform-state-bucket"
key = "path/to/my/network-terraform.tfstate"
region = "us-east-1"
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
}The consumer needs the right permissions to read the state files in the bucket.
apply operations can corrupt state.terraform workspace commands map each workspace to a distinct key within the bucket.Platforms like Scalr and Terraform Cloud take most of the learning curve out of state management by giving you a central, managed backend. They handle secure remote storage, state locking, and versioning for easy rollbacks out of the box. They also support run triggers, which kick off a run in a downstream workspace whenever its upstream workspace finishes an apply.
Both platforms hide the messy parts of manual state management behind workspaces. A workspace is the basic unit of organization, an isolated container for one set of infrastructure, where the state and all the related deployment artifacts live.
Terraform Cloud is HashiCorp's managed service for a collaborative Terraform workflow. State management is a core feature:

Once state sharing is configured, consumers use the terraform_remote_state data source:
# Workspace A (e.g., networking)
output "vpc_id" {
value = aws_vpc.main.id
}
output "public_subnet_ids" {
value = aws_subnet.public.*.id
}
# Workspace B (e.g., compute), consuming outputs from Workspace A
data "terraform_remote_state" "network" {
backend = "remote"
config = {
organization = "your-organization-name"
workspaces {
name = "workspace-a-name"
}
}
}
resource "aws_instance" "web" {
ami = "ami-0abcdef1234567890"
instance_type = "t2.micro"
subnet_id = data.terraform_remote_state.network.outputs.public_subnet_ids[0]
}Terraform Cloud securely pulls the state from the upstream workspace and hands its outputs to the downstream one.
Scalr is another Terraform automation platform with enterprise-grade state management and sharing:

Once a workspace is granted permission to pull outputs from another workspace's state, consumers define the data source.

The configuration is very similar to Terraform Cloud:
# Scalr Workspace A (e.g., base-infrastructure)
output "vpc_id" {
value = aws_vpc.main.id
}
# Scalr Workspace B (e.g., application-deployment)
data "terraform_remote_state" "base_infra" {
backend = "remote"
config = {
organization = "your-scalr-environment-id"
workspaces {
name = "base-infrastructure-workspace-name"
}
}
}
resource "aws_instance" "app" {
ami = "ami-0abcdef1234567890"
instance_type = "t2.micro"
subnet_id = data.terraform_remote_state.base_infra.outputs.public_subnet_ids[0]
}Note: the organization value in terraform_remote_state for Scalr will be your Scalr environment ID. See the Scalr documentation for the precise value.
Sharing outputs is only half the story. A successful apply changes the state file's outputs, and those changes still need to reach the workspaces that consume them automatically.
Run triggers, available in both Terraform Cloud and Scalr, chain workspace runs together. They set up an explicit dependency: when terraform apply finishes in an "upstream" workspace, it kicks off a new run in a "downstream" one.

Run triggers let you orchestrate deployments and skip the monolithic config, breaking things into smaller workspaces that depend on each other. That's much easier to manage and nicer to work with.
If you're rolling your own with open-source Terraform, you can get close to this with CI/CD glue. Here's a GitHub Actions sketch that uses repository_dispatch to chain a network-infra repo to an app-servers repo:
# network-infra/.github/workflows/deploy-network.yml
name: Deploy Network Infrastructure
on:
push:
branches: [main]
paths: ['network-infra/**']
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:
- uses: actions/checkout@v4
- uses: hashicorp/setup-terraform@v3
- 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
- run: terraform apply -auto-approve
working-directory: network-infra
- name: Trigger app-servers
if: success()
uses: peter-evans/repository-dispatch@v3
with:
token: ${{ secrets.PAT_TOKEN }}
repository: your-org/app-servers
event-type: deploy-app-servers# app-servers/.github/workflows/deploy-app-servers.yml
name: Deploy Application Servers
on:
push:
branches: [main]
paths: ['app-servers/**']
repository_dispatch:
types: [deploy-app-servers]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: hashicorp/setup-terraform@v3
- run: terraform init -backend-config="bucket=my-tf-states" -backend-config="key=prod/app-servers/terraform.tfstate"
working-directory: app-servers
- run: terraform apply -auto-approve
working-directory: app-serversThis gets the job done, but the glue and the credentials it needs add real maintenance work. That's where built-in Scalr federated environments, run triggers, and state sharing come in.
Whether you wire up your own remote backend or use a managed platform, the same practices keep shared state safe: keep state files out of version control, turn on encryption and locking, scope access to least privilege, and enable versioning so you can roll back. Platforms like Terraform Cloud and Scalr handle that plumbing for you, and Scalr's federated environments and run triggers add controlled cross-environment access on top.
Try Scalr for free.
