MARGA by EknathaLabs

Terraform cheat sheet

Workflow commands, HCL syntax, state operations, meta-arguments and safe pipeline practices.

Workflow

terraform init
terraform fmt -recursive
terraform validate
terraform plan -out=tfplan
terraform apply tfplan
terraform destroy
terraform output
terraform output -raw bucket_name
Plan symbol Meaning
+ Create
~ Update in place
- Destroy
-/+ Replace (destroy and recreate)

Read the summary line and every - or -/+ before you apply.

Configuration skeleton

terraform {
  required_version = ">= 1.6"
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}

provider "aws" {
  region = var.region
}

variable "region" {
  type    = string
  default = "eu-west-1"
}

resource "aws_s3_bucket" "logs" {
  bucket = "acme-logs-${var.environment}"
}

output "bucket_arn" {
  value = aws_s3_bucket.logs.arn
}

Variables

variable "environment" {
  type        = string
  description = "dev, staging or prod"
  validation {
    condition     = contains(["dev", "staging", "prod"], var.environment)
    error_message = "Must be dev, staging or prod."
  }
}

variable "db_password" {
  type      = string
  sensitive = true
}

Precedence (low to high): default, TF_VAR_name, terraform.tfvars, -var-file, -var.

Expressions

locals {
  prefix = "shop-${var.environment}"
  is_prod = var.environment == "prod"
  names   = [for n in var.names : upper(n)]
  size    = local.is_prod ? "m5.large" : "t3.micro"
}
Function Example
merge(a, b) Combine maps
lookup(map, key, default) Safe map access
cidrsubnet(cidr, 8, 1) Carve a subnet
join(",", list) Make a string
try(expr, fallback) Fall back on error

Experiment with terraform console.

Meta-arguments

resource "aws_subnet" "multi" {
  for_each          = var.subnets          # map or set, stable keys
  vpc_id            = aws_vpc.main.id
  cidr_block        = each.value
}

resource "aws_instance" "web" {
  count = var.enabled ? 1 : 0              # on/off switch
  lifecycle {
    prevent_destroy       = true
    create_before_destroy = true
    ignore_changes        = [tags]
  }
}

Prefer for_each to count for collections; count shifts indexes when an item is removed.

State

terraform state list
terraform state show aws_s3_bucket.logs
terraform state rm aws_s3_bucket.legacy     # forget, do not delete
terraform apply -refresh-only               # record reality without changing it
terraform force-unlock LOCK_ID              # only when no run is active
moved {
  from = aws_s3_bucket.logs
  to   = aws_s3_bucket.audit_logs
}

import {
  to = aws_s3_bucket.legacy
  id = "acme-legacy-reports"
}

Remote backend

terraform {
  backend "s3" {
    bucket       = "acme-terraform-state"
    key          = "shop/prod/terraform.tfstate"
    region       = "eu-west-1"
    encrypt      = true
    use_lockfile = true
  }
}

Enable bucket versioning, restrict access, and keep one state per environment.

Modules

module "site" {
  source  = "../../modules/static-site"
  name    = "marketing-prod"
}

output "site_bucket" {
  value = module.site.bucket_name
}

Pin versions of modules you do not own. Run terraform init after adding a module.

Pipeline checks

terraform fmt -check -recursive
terraform validate
tflint
trivy config .
terraform plan -detailed-exitcode     # 0 none, 1 error, 2 changes
terraform test

Workflow with a saved plan

terraform fmt -recursive
terraform validate
terraform init -upgrade
terraform plan -out=tfplan -input=false
terraform show tfplan
terraform show -json tfplan | jq '.resource_changes[] | select(.change.actions != ["no-op"]) | {a: .address, c: .change.actions}'
terraform apply tfplan
terraform output -json
terraform providers
terraform version

State inspection and surgery

terraform state list
terraform state show aws_instance.web
terraform state pull > backup.tfstate
terraform state mv aws_instance.web aws_instance.app
terraform state rm aws_instance.old
terraform import aws_s3_bucket.logs acme-logs
terraform force-unlock LOCK_ID
terraform refresh
terraform taint aws_instance.web
terraform apply -replace=aws_instance.web

Targeting, variables and workspaces

terraform plan -target=module.network
terraform plan -var 'env=staging' -var-file=staging.tfvars
terraform workspace list
terraform workspace new staging
terraform workspace select prod
terraform console
export TF_LOG=INFO TF_LOG_PATH=tf.log
TF_VAR_db_password="$DB_PASSWORD" terraform plan

Destroy

terraform plan -destroy -out=destroy.tfplan
terraform show destroy.tfplan | less
terraform destroy -target=aws_instance.test
lifecycle { prevent_destroy = true }

Official references

Read the vendor and project documentation for the full, current detail.