Terraform S3 + DynamoDB setup

The Terraform code below shows the necessary resources to create infrastructure on AWS that will store a projects state file.


provider "aws" {
  region = "eu-west-1"
}

resource "aws_s3_bucket" "terraform_state" {
  bucket = "terraform-project-name-bucket"  

lifecycle {
    prevent_destroy = true
  }
}



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

//enable server-side encryption by default. 

resource "aws_s3_bucket_server_side_encryption_configuration" "default" { 
  bucket = aws_s3_bucket.terraform_state.id 

  rule {
    apply_server_side_encryption_by_default {
      sse_algorithm = "AES256"
    }
  }
}

//block public access to the s3 bucket.

resource "aws_s3_bucket_public_access_block" "public_access" {
  bucket                  = aws_s3_bucket.terraform_state.id
  block_public_acls       = true
  block_public_policy     = true
  ignore_public_acls      = true
  restrict_public_buckets = true
}

//DynamoDB lock

resource "aws_dynamodb_table" "terraform_locks" {
  name          = "terraform-project-name-lock"
  billing_mode  = "PAY_PER_REQUEST"
  hash_key      = "LockID"
  
  attribute {
    name = "LockID"
    type = "S"
  }
} 

Your projects can use a backend.tf file. When running terraform init, the file will be read and will connect to S3 to store the state file.


terraform {
  backend "s3" {
    bucket          = "terraform-project-name-bucket"
    key             = "global/s3/terraform.tfstate"
    region          = "eu-west-1"
    
    dynamodb_table  = "terraform-project-name-lock" 
    encrypt         = true
  }
}

Creating a state file for each project environment, dev, staging and production creates environment separation, preventing unintended impact through a misconfigured resource.

└── environments
    ├── dev
    │   ├── backend.tf
    │   ├── main.tf
    │   ├── outputs.tf
    │   ├── terraform.tfvars
    │   └── variables.tf
    ├── staging
    │   ├── backend.tf
    │   ├── main.tf
    │   ├── outputs.tf
    │   ├── terraform.tfvars
    │   └── variables.tf
    └── prod
        ├── backend.tf
        ├── main.tf
        ├── outputs.tf
        ├── terraform.tfvars
        └── variables.tf

Implementing state file isolation allows teams to work on their infrastructure components enabling changes to occur without blocking others and shows clear team boundaries of ownership and responsibility. Larger organisations will have the infrastructure environments managed over multiple accounts.

    key = "global/s3/terraform.tfstate"
    key = "dev/app/terraform.tfstate"
    key = "prod/networking/terraform.tfstate"

Breaking up the component file structure also has the advantage of reducing complexity, rather than storing all resources together, for example deployments for networking [VPC, subnets], applications, and databases [RDS] can be separated. Terraform plans and applies will run quicker, change complexity is reduced as your only changing a specific resource for example an application.