Terraform Remote State
Introduction
When working with Terraform, state files are crucial as they track all the resources Terraform manages. By default, Terraform stores state locally in a file named terraform.tfstate
. While this works for personal projects, it creates challenges when:
- Multiple team members need to apply changes
- You're managing infrastructure across different environments
- You need to ensure state file security and integrity
- You want to implement continuous integration/continuous deployment (CI/CD) pipelines
This is where Terraform Remote State comes in. Remote state allows you to store your state files in a shared location that can be accessed by all team members and automation tools.
Understanding Remote State
What is Remote State?
Remote state is a Terraform feature that enables storing state files in remote data stores such as:
- AWS S3
- Azure Blob Storage
- Google Cloud Storage
- HashiCorp Consul
- HashiCorp Terraform Cloud
- And many others
Why Use Remote State?
Remote state provides several benefits:
- Collaboration - Team members can work together without state file conflicts
- Locking - Prevents concurrent modifications that could corrupt the state
- Security - Can implement encryption and access controls
- Versioning - Many backends support versioning of state files
- Disaster Recovery - Remote storage is typically more reliable than local storage
Configuring Remote State
Basic Configuration
To set up remote state, you need to configure a backend in your Terraform configuration. Here's a basic example using AWS S3:
terraform {
backend "s3" {
bucket = "my-terraform-state"
key = "prod/terraform.tfstate"
region = "us-west-2"
}
}
Common Backend Types
Let's explore some common backend configurations:
AWS S3
terraform {
backend "s3" {
bucket = "my-terraform-state"
key = "prod/network/terraform.tfstate"
region = "us-east-1"
dynamodb_table = "terraform-locks"
encrypt = true
}
}
This configuration:
- Stores state in an S3 bucket called
my-terraform-state
- Saves the state file at the path
prod/network/terraform.tfstate
- Uses DynamoDB for state locking
- Encrypts the state file
Azure Storage
terraform {
backend "azurerm" {
resource_group_name = "tfstate"
storage_account_name = "tfstate1234"
container_name = "tfstate"
key = "prod.terraform.tfstate"
}
}
Google Cloud Storage
terraform {
backend "gcs" {
bucket = "tf-state-prod"
prefix = "terraform/state"
}
}
HashiCorp Terraform Cloud
terraform {
cloud {
organization = "my-company"
workspaces {
name = "my-app-prod"
}
}
}
Remote State Workflow
Here's the typical workflow when using remote state:
Step-by-Step Implementation
Let's walk through setting up remote state with AWS S3:
- Create the S3 bucket and DynamoDB table
First, you need to create the infrastructure to store your state. You can use Terraform itself to do this:
provider "aws" {
region = "us-west-2"
}
resource "aws_s3_bucket" "terraform_state" {
bucket = "my-terraform-state"
lifecycle {
prevent_destroy = true
}
}
resource "aws_s3_bucket_versioning" "terraform_state" {
bucket = aws_s3_bucket.terraform_state.id
versioning_configuration {
status = "Enabled"
}
}
resource "aws_s3_bucket_server_side_encryption_configuration" "terraform_state" {
bucket = aws_s3_bucket.terraform_state.id
rule {
apply_server_side_encryption_by_default {
sse_algorithm = "AES256"
}
}
}
resource "aws_dynamodb_table" "terraform_locks" {
name = "terraform-locks"
billing_mode = "PAY_PER_REQUEST"
hash_key = "LockID"
attribute {
name = "LockID"
type = "S"
}
}
- Apply the configuration to create the bucket and table
terraform init
terraform apply
- Configure the backend in your main Terraform configuration
Create a new file or update your existing configuration:
terraform {
backend "s3" {
bucket = "my-terraform-state"
key = "prod/terraform.tfstate"
region = "us-west-2"
dynamodb_table = "terraform-locks"
encrypt = true
}
}
# Rest of your Terraform configuration...
- Initialize Terraform with the new backend
terraform init
Terraform will prompt you to migrate your state to the new backend.
- Verify that the state is now stored remotely
The local terraform.tfstate
file will be replaced with a minimal file referencing the remote state.
Practical Example: Multi-Environment Infrastructure
Let's look at a real-world example where we manage infrastructure for multiple environments:
Project Structure
project/
├── environments/
│ ├── dev/
│ │ ├── main.tf
│ │ └── variables.tf
│ └── prod/
│ ├── main.tf
│ └── variables.tf
├── modules/
│ ├── networking/
│ │ ├── main.tf
│ │ ├── outputs.tf
│ │ └── variables.tf
│ └── web_app/
│ ├── main.tf
│ ├── outputs.tf
│ └── variables.tf
└── backend/
└── main.tf
Backend Configuration
First, create the shared infrastructure for state storage (backend/main.tf
):
provider "aws" {
region = "us-west-2"
}
resource "aws_s3_bucket" "terraform_state" {
bucket = "my-company-terraform-state"
}
resource "aws_s3_bucket_versioning" "terraform_state" {
bucket = aws_s3_bucket.terraform_state.id
versioning_configuration {
status = "Enabled"
}
}
resource "aws_s3_bucket_server_side_encryption_configuration" "terraform_state" {
bucket = aws_s3_bucket.terraform_state.id
rule {
apply_server_side_encryption_by_default {
sse_algorithm = "AES256"
}
}
}
resource "aws_dynamodb_table" "terraform_locks" {
name = "terraform-locks"
billing_mode = "PAY_PER_REQUEST"
hash_key = "LockID"
attribute {
name = "LockID"
type = "S"
}
}
output "s3_bucket_name" {
value = aws_s3_bucket.terraform_state.bucket
}
output "dynamodb_table_name" {
value = aws_dynamodb_table.terraform_locks.name
}
Development Environment Configuration
In your development environment (environments/dev/main.tf
):
terraform {
backend "s3" {
bucket = "my-company-terraform-state"
key = "dev/terraform.tfstate"
region = "us-west-2"
dynamodb_table = "terraform-locks"
encrypt = true
}
}
provider "aws" {
region = "us-west-2"
}
module "networking" {
source = "../../modules/networking"
vpc_cidr = "10.0.0.0/16"
environment = "dev"
}
module "web_app" {
source = "../../modules/web_app"
vpc_id = module.networking.vpc_id
subnet_ids = module.networking.subnet_ids
environment = "dev"
instance_type = "t2.micro"
}
Production Environment Configuration
In your production environment (environments/prod/main.tf
):
terraform {
backend "s3" {
bucket = "my-company-terraform-state"
key = "prod/terraform.tfstate"
region = "us-west-2"
dynamodb_table = "terraform-locks"
encrypt = true
}
}
provider "aws" {
region = "us-west-2"
}
module "networking" {
source = "../../modules/networking"
vpc_cidr = "10.1.0.0/16"
environment = "prod"
}
module "web_app" {
source = "../../modules/web_app"
vpc_id = module.networking.vpc_id
subnet_ids = module.networking.subnet_ids
environment = "prod"
instance_type = "t2.large"
}
Working with Remote State Data
One of the powerful features of Terraform remote state is the ability to access outputs from other Terraform configurations. This enables you to share information between different parts of your infrastructure.
State Data Sources
You can use the terraform_remote_state
data source to access outputs from another Terraform configuration:
data "terraform_remote_state" "network" {
backend = "s3"
config = {
bucket = "my-terraform-state"
key = "network/terraform.tfstate"
region = "us-west-2"
}
}
resource "aws_instance" "app" {
# Use an output from the networking state
subnet_id = data.terraform_remote_state.network.outputs.subnet_id
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t2.micro"
}
This pattern allows you to maintain separation of concerns while still allowing different parts of your infrastructure to reference each other.
Best Practices for Remote State
-
Use State Locking: Always enable state locking to prevent concurrent modifications.
-
Enable Encryption: Encrypt your state files since they may contain sensitive information.
-
Use Workspaces or Separate State Files: For different environments, use either:
- Terraform workspaces
- Separate state files with different keys
-
Restrict Access: Implement access controls on your remote state backend.
-
Enable Versioning: Turn on versioning for your storage to recover from accidental changes.
-
Back Up Your State: Even with remote state, implement regular backups.
-
Don't Store Secrets in Outputs: Be careful not to expose sensitive data in outputs that other states might access.
Migrating from Local to Remote State
If you've been using local state and want to migrate to remote state, follow these steps:
-
Add the backend configuration to your Terraform files.
-
Run
terraform init
. Terraform will detect the change and prompt you:
Backend configuration changed!
Terraform has detected that the configuration specified for the backend
has changed. Terraform will now check for existing state in the backends.
Do you want to copy existing state to the new backend?
Pre-existing state was found while migrating the previous "local" backend to the
newly configured "s3" backend. No existing state was found in the newly
configured "s3" backend. Do you want to copy this state to the new "s3"
backend? Enter "yes" to copy and "no" to start with an empty state.
Enter a value: yes
-
Type "yes" to migrate your state to the new backend.
-
Verify the migration by checking that your state is now in the remote location.
Troubleshooting Remote State
Here are some common issues and their solutions:
"Error loading state: AccessDenied"
This usually means your authentication credentials don't have enough permissions to access the remote state.
Solution: Verify your IAM permissions or access credentials.
"Error acquiring the state lock"
This happens when someone else is currently modifying the state, or if a previous Terraform process was interrupted before releasing the lock.
Solution: If you're sure no one else is applying changes, you can force unlock the state:
terraform force-unlock <LOCK_ID>
"Backend configuration changed"
This occurs when you change backend settings.
Solution: Follow the prompts to migrate your state to the new backend configuration.
Summary
Terraform Remote State is an essential feature for teams working with Terraform. It enables collaboration, provides security, and enhances reliability for your infrastructure management. By storing state remotely, you can:
- Collaborate effectively with team members
- Implement proper state locking
- Keep your state files secure
- Access outputs across different Terraform configurations
- Build reliable CI/CD pipelines
As your infrastructure grows in complexity, remote state becomes increasingly important. Starting with good remote state practices early in your Terraform journey will save you considerable headaches down the road.
Additional Resources
Exercises
- Set up a remote state backend using AWS S3 and DynamoDB.
- Create a multi-environment project structure with separate state files for dev and prod.
- Use the
terraform_remote_state
data source to access outputs from another configuration. - Practice migrating a local state to remote state.
- Set up a CI/CD pipeline that uses remote state to deploy infrastructure changes.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)