This article was translated using AI.

Reference: Terraform Up & Running (O’Reilly)

Storing Terraform state locally is fine for solo projects, but teams need a shared backend. On AWS, an S3 bucket (state files) plus DynamoDB table (state locking) works well.


Provision S3 & DynamoDB

Create a separate Terraform project to bootstrap the backend:

provider "aws" {
  region = "ap-northeast-2"
}

resource "aws_s3_bucket" "terraform_state" {
  bucket = "jeongbin-terraform-bucket"
  lifecycle {
    prevent_destroy = true
  }
}

resource "aws_s3_bucket_versioning" "versioning" {
  bucket = aws_s3_bucket.terraform_state.id
  versioning_configuration {
    status = "Enabled"
  }
}

resource "aws_s3_bucket_server_side_encryption_configuration" "encryption" {
  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-up-and-running-locks"
  billing_mode = "PAY_PER_REQUEST"
  hash_key     = "LockID"

  attribute {
    name = "LockID"
    type = "S"
  }
}

Apply this once to create the bucket/table.


Configure the Backend

In your main Terraform project:

terraform {
  backend "s3" {
    bucket         = "jeongbin-terraform-bucket"
    key            = "global/s3/terraform.tfstate"
    region         = "ap-northeast-2"
    dynamodb_table = "terraform-up-and-running-locks"
    encrypt        = true
  }
}

Run terraform init to migrate state to S3. You’ll notice no local .tfstate file—Terraform now reads/writes to S3.

To avoid hardcoding values, use an HCL config file:

# backend.hcl
bucket         = "jeongbin-terraform-bucket"
region         = "ap-northeast-2"
dynamodb_table = "terraform-up-and-running-locks"
encrypt        = true
# backend.tf
terraform {
  backend "s3" {
    key = "global/s3/terraform.tfstate"
  }
}

Initialize with:

terraform init -backend-config=backend.hcl

Workspaces (Environment Isolation)

Terraform workspaces provide isolated state files—handy for staging vs. production.

terraform workspace show        # current workspace
terraform workspace new dev     # create a new workspace
terraform apply                 # applies only to that workspace
terraform workspace select default  # switch back

In S3 you’ll see separate folders (e.g., env:/dev/terraform.tfstate).

You can even tailor resources per workspace:

resource "aws_instance" "ec2" {
  ami           = "ami-0ea5eb4b05645aa8a"
  instance_type = terraform.workspace == "default" ? "t3.nano" : "t3.medium"
}

This ternary-like expression picks a larger instance type outside the default workspace.