Terraform State
Introduction
Terraform state is one of the most important concepts to understand when working with Terraform. It's the mechanism that Terraform uses to keep track of the resources it manages and the relationships between them.
In this guide, you'll learn:
- What Terraform state is and why it's essential
- How Terraform uses state to manage infrastructure
- Different backends for storing state
- State locking and workspaces
- Best practices for state management
What is Terraform State?
At its core, Terraform state is a mapping between your configured resources and the real-world infrastructure objects they represent. Terraform stores this mapping in a file called terraform.tfstate
by default.
Let's understand why state is necessary:
- Resource Tracking: State keeps track of which real-world resources correspond to which resources in your configuration
- Metadata Storage: It stores metadata about your resources that can't be inferred from the infrastructure alone
- Performance: State helps optimize performance by caching resource attributes
- Collaboration: State facilitates team collaboration by providing a shared view of your infrastructure
Example of a Terraform State File
When you run terraform apply
, Terraform creates or updates a state file. Here's a simplified example of what a state file looks like:
{
"version": 4,
"terraform_version": "1.5.7",
"serial": 1,
"lineage": "3f200cbc-3bec-643f-2a84-5793b6a9ca2f",
"outputs": {},
"resources": [
{
"mode": "managed",
"type": "aws_instance",
"name": "example",
"provider": "provider[\"registry.terraform.io/hashicorp/aws\"]",
"instances": [
{
"schema_version": 1,
"attributes": {
"ami": "ami-0c55b159cbfafe1f0",
"instance_type": "t2.micro",
"id": "i-0123456789abcdef0",
"tags": {
"Name": "example-instance"
}
}
}
]
}
]
}
This file contains detailed information about the resources Terraform is managing, including resource types, names, attributes, and dependencies.
How Terraform Uses State
The Terraform Workflow
Let's look at how state fits into the Terraform workflow:
-
When you run
terraform plan
, Terraform:- Reads your configuration files
- Reads the current state file
- Queries providers to find the real state of resources
- Determines what changes need to be made
-
When you run
terraform apply
, Terraform:- Makes the necessary changes to your infrastructure
- Updates the state file with the new resource information
Example: Adding a Resource
Let's see how Terraform uses state when adding a new resource:
# main.tf
provider "aws" {
region = "us-west-2"
}
resource "aws_instance" "example" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t2.micro"
tags = {
Name = "example-instance"
}
}
When you run terraform apply
with this configuration:
- Terraform checks if this resource exists in the state
- Since it doesn't exist, Terraform creates the AWS instance
- Terraform adds the resource to the state file
- Future operations will use this state entry to track the resource
Terraform State Storage: Backends
By default, Terraform stores state locally in a file called terraform.tfstate
. However, this approach has limitations for team environments. Terraform supports different "backends" for storing state remotely.
Popular Backends
- Local: The default backend, stores state in a local file
- S3: Store state in an AWS S3 bucket
- Azure Storage: Store state in Azure Blob Storage
- Google Cloud Storage: Store state in a GCS bucket
- Terraform Cloud: HashiCorp's managed service for Terraform
- HTTP: Store state using a REST client
Configuring a Remote Backend
Here's how to configure an S3 backend:
# backend.tf
terraform {
backend "s3" {
bucket = "my-terraform-state"
key = "prod/terraform.tfstate"
region = "us-east-1"
dynamodb_table = "terraform-locks"
encrypt = true
}
}
After configuring a backend, run terraform init
to initialize it:
$ terraform init
Initializing the backend...
Successfully configured the backend "s3"! Terraform will automatically
use this backend unless the backend configuration changes.
State Locking
When working in a team, multiple people might try to modify the same infrastructure simultaneously. To prevent conflicts, Terraform uses state locking.
State locking ensures that only one operation can modify the state at a time, preventing corruption or conflicts. Different backends support different locking mechanisms:
- S3: Uses DynamoDB for locking
- Azure: Uses blob leases
- Terraform Cloud: Has built-in locking
Example: S3 Backend with DynamoDB Locking
terraform {
backend "s3" {
bucket = "my-terraform-state"
key = "prod/terraform.tfstate"
region = "us-east-1"
dynamodb_table = "terraform-locks" # Table used for locking
encrypt = true
}
}
Create the DynamoDB table:
resource "aws_dynamodb_table" "terraform_locks" {
name = "terraform-locks"
billing_mode = "PAY_PER_REQUEST"
hash_key = "LockID"
attribute {
name = "LockID"
type = "S"
}
}
Terraform Workspaces
Workspaces allow you to manage multiple states with the same configuration. This is useful for managing different environments (development, staging, production) with the same code.
Basic Workspace Commands
# List workspaces
$ terraform workspace list
* default
# Create a new workspace
$ terraform workspace new dev
Created and switched to workspace "dev"!
# Switch between workspaces
$ terraform workspace select prod
Switched to workspace "prod".
Using Workspaces in Configuration
You can reference the current workspace in your configuration:
resource "aws_instance" "example" {
count = terraform.workspace == "prod" ? 2 : 1
ami = "ami-0c55b159cbfafe1f0"
instance_type = terraform.workspace == "prod" ? "t2.medium" : "t2.micro"
tags = {
Name = "example-${terraform.workspace}"
Environment = terraform.workspace
}
}
Terraform State Commands
Terraform provides several commands for managing state:
Inspecting State
# List all resources in state
$ terraform state list
aws_instance.example
# Show details about a specific resource
$ terraform state show aws_instance.example
# aws_instance.example:
resource "aws_instance" "example" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t2.micro"
...
}
Manipulating State
# Move a resource in state (rename)
$ terraform state mv aws_instance.example aws_instance.web
# Remove a resource from state (without destroying it)
$ terraform state rm aws_instance.example
# Import existing infrastructure into state
$ terraform import aws_instance.imported i-0123456789abcdef0
Best Practices for State Management
- Use Remote Backends: Always use a remote backend for team environments
- Enable Encryption: Make sure your state is encrypted, especially when it contains sensitive data
- Enable Locking: Use a backend that supports locking to prevent conflicts
- Separate State by Environment: Use different state files for development, staging, and production
- Backup Your State: Regularly backup your state files or use a backend with versioning
- Restrict Access: Limit who can access and modify your state files
- Don't Edit State Manually: Never modify the state file by hand; use Terraform commands
State Isolation Strategies
There are three main approaches to separating state by environment:
- Directories: Separate configuration directories for each environment
- Workspaces: Use Terraform workspaces for simpler environments
- File Layout: Use a consistent file structure with environment-specific configurations
Example directory structure:
terraform/
├── modules/
│ ├── vpc/
│ ├── ec2/
│ └── rds/
├── environments/
│ ├── dev/
│ │ ├── main.tf
│ │ └── backend.tf
│ ├── staging/
│ │ ├── main.tf
│ │ └── backend.tf
│ └── prod/
│ ├── main.tf
│ └── backend.tf
└── global/
└── iam/
└── main.tf
Real-world Example: Web Application Infrastructure
Let's put it all together with a real-world example of managing state for a web application infrastructure:
# backend.tf
terraform {
backend "s3" {
bucket = "company-terraform-state"
key = "webapp/prod/terraform.tfstate"
region = "us-east-1"
dynamodb_table = "terraform-locks"
encrypt = true
}
}
# main.tf
provider "aws" {
region = "us-east-1"
}
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "3.14.0"
name = "webapp-vpc"
cidr = "10.0.0.0/16"
azs = ["us-east-1a", "us-east-1b", "us-east-1c"]
private_subnets = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]
public_subnets = ["10.0.101.0/24", "10.0.102.0/24", "10.0.103.0/24"]
enable_nat_gateway = true
single_nat_gateway = false
tags = {
Environment = "prod"
Terraform = "true"
}
}
resource "aws_security_group" "web" {
name = "web-sg"
description = "Security group for web servers"
vpc_id = module.vpc.vpc_id
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
resource "aws_instance" "web" {
count = 2
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t2.micro"
subnet_id = module.vpc.public_subnets[count.index % 3]
vpc_security_group_ids = [aws_security_group.web.id]
tags = {
Name = "web-server-${count.index + 1}"
Environment = "prod"
}
}
output "web_public_ips" {
value = aws_instance.web[*].public_ip
}
Handling State Errors
Sometimes you might encounter state errors. Here are some common issues and solutions:
State Locking Errors
If Terraform can't acquire a lock, you might see:
Error: Error locking state: Error acquiring the state lock: ConditionalCheckFailedException
This usually means someone else is running Terraform. If you're sure no one else is, you can use the -force-unlock
command:
$ terraform force-unlock LOCK_ID
State Corruption
If your state file gets corrupted:
- If using a remote backend with versioning, restore from a previous version
- If you have a backup, restore from the backup
- As a last resort, you might need to recreate your state by importing resources
# Remove the corrupted state
$ rm terraform.tfstate
# Import each resource
$ terraform import aws_instance.example i-0123456789abcdef0
Summary
Terraform state is a crucial component that keeps track of the resources managed by Terraform. Key points to remember:
- State maps your configuration to real-world resources
- Remote backends provide collaboration features and improved security
- State locking prevents conflicts in team environments
- Workspaces help manage multiple environments
- Following best practices for state management is essential for production environments
By understanding and properly managing Terraform state, you'll avoid many common pitfalls and create more maintainable infrastructure code.
Additional Resources
Exercises
- Set up a local Terraform configuration with an S3 backend and DynamoDB locking.
- Create different workspaces for dev, staging, and production environments.
- Practice importing an existing cloud resource into your Terraform state.
- Try using state commands like
terraform state list
andterraform state show
to explore your state. - Implement a module structure for a web application with proper state separation.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)