Terraform Associate - High-Yield Scenarios and Patterns¶
State Management Scenarios¶
Remote State Migration¶
Scenario: Your team has been using local state for a Terraform project. Multiple engineers now need to collaborate, and you need to prevent concurrent modifications. How should you configure remote state with locking?
Solution Pattern: - Backend: Configure S3 backend with DynamoDB table for locking - Migration: Run terraform init -migrate-state to move state - Locking: DynamoDB table provides automatic state locking - Encryption: Enable server-side encryption on S3 bucket
terraform {
backend "s3" {
bucket = "my-terraform-state"
key = "prod/terraform.tfstate"
region = "us-east-1"
dynamodb_table = "terraform-locks"
encrypt = true
}
}
Common Distractors: - Using S3 alone without DynamoDB (wrong - S3 does not provide native state locking) - Using terraform state push to migrate (wrong - use terraform init -migrate-state) - Storing state in version control (wrong - state contains sensitive data, not suitable for VCS) - Using terraform init -reconfigure for migration (wrong - this discards existing state)
State Drift Detection¶
Scenario: You suspect someone manually modified an AWS security group that Terraform manages. How do you detect and reconcile the drift?
Solution Pattern: - Detection: Run terraform plan -refresh-only to detect drift - Review: Examine the proposed state changes - Reconcile: Either apply the refresh to update state, or run terraform apply to revert to desired configuration - Prevention: Implement policies to prevent manual changes
Common Distractors: - Running terraform refresh directly (wrong - deprecated, use plan -refresh-only) - Deleting and reimporting the resource (wrong - unnecessary and disruptive) - Editing the state file manually (wrong - never edit state files directly) - Running terraform taint on the resource (wrong - taint forces replacement, not reconciliation)
Module Design Scenarios¶
Reusable Multi-Environment Module¶
Scenario: You need to deploy the same infrastructure pattern across dev, staging, and production environments with different configurations for each.
Solution Pattern: - Module: Create a reusable module with configurable variables - Environments: Use separate variable files or workspaces - Versioning: Pin module versions for stability - Outputs: Export resource attributes for cross-module references
module "app" {
source = "./modules/app-stack"
environment = "production"
instance_type = "t3.large"
min_capacity = 3
max_capacity = 10
enable_waf = true
}
Common Distractors: - Duplicating entire configurations per environment (wrong - violates DRY principle) - Using count on modules for environments (wrong - workspaces or separate module calls are cleaner) - Hardcoding values inside modules (wrong - use variables for configurability) - Using a single workspace for all environments (wrong - environments should be isolated)
Module Source Selection¶
Scenario: Your organization wants to use a community VPC module but needs to ensure stability. The team wants to control which version is deployed and prevent unexpected breaking changes.
Solution Pattern: - Source: Use Terraform Registry with version constraint - Constraint: Use pessimistic version constraint ~> for minor version flexibility - Lock file: Commit .terraform.lock.hcl for reproducible builds - Review: Check module changelog before upgrading
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "~> 5.0"
# Allows 5.x but not 6.0
}
Common Distractors: - Using latest as version (wrong - no such syntax, and pins should be explicit) - Omitting version constraint entirely (wrong - always latest could break builds) - Copying module source into your repo (wrong - loses upstream updates and community fixes) - Using exact version = 5.0.0 always (wrong - misses bug fixes in patch versions)
Workflow Scenarios¶
Failed Apply Recovery¶
Scenario: A terraform apply partially completed before failing due to an API rate limit. Some resources were created, others were not. How do you recover?
Solution Pattern: - State: Terraform automatically tracks partially created resources in state - Re-run: Simply run terraform apply again - Idempotent: Terraform will skip already-created resources and create remaining ones - Review: Run terraform plan first to verify expected changes
Common Distractors: - Running terraform destroy and starting over (wrong - destroys already-created resources unnecessarily) - Manually deleting created resources (wrong - causes state drift) - Editing state to remove failed resources (wrong - dangerous and unnecessary) - Running terraform init again (wrong - init does not fix partial applies)
Import Existing Infrastructure¶
Scenario: Your team manually created an RDS database in AWS. You need to bring it under Terraform management without downtime or recreation.
Solution Pattern: - Write config: Create the resource block in Terraform configuration - Import: Use import block or terraform import command - Verify: Run terraform plan to ensure no changes detected - Adjust: Modify configuration until plan shows no changes
import {
to = aws_db_instance.main
id = "my-database-identifier"
}
resource "aws_db_instance" "main" {
identifier = "my-database-identifier"
engine = "mysql"
instance_class = "db.t3.medium"
# ... match existing configuration
}
Common Distractors: - Import generates configuration automatically (wrong - you must write or generate-config-out the config) - Import creates the resource in AWS (wrong - import only adds to state) - Using terraform state mv to import (wrong - mv moves within state, not from external) - Importing requires downtime (wrong - import is a state-only operation)
Provider Configuration Scenarios¶
Multi-Region Deployment¶
Scenario: You need to deploy resources in both us-east-1 and eu-west-1 using a single Terraform configuration.
Solution Pattern: - Aliases: Configure provider aliases for each region - Resources: Specify provider for each resource - Modules: Pass provider configurations to modules using providers argument
provider "aws" {
region = "us-east-1"
}
provider "aws" {
alias = "eu"
region = "eu-west-1"
}
resource "aws_instance" "us_server" {
ami = "ami-0123456789"
instance_type = "t3.micro"
}
resource "aws_instance" "eu_server" {
provider = aws.eu
ami = "ami-9876543210"
instance_type = "t3.micro"
}
Common Distractors: - Creating separate configurations per region (wrong - can use aliases in one config) - Using variables for region in a single provider (wrong - only handles one region at a time) - Using workspaces for multi-region (wrong - workspaces are for same config different state) - Omitting the alias attribute (wrong - cannot have two default providers of same type)
Provider Version Conflict¶
Scenario: After upgrading the AWS provider version, terraform init fails with a version constraint error from a module dependency.
Solution Pattern: - Diagnose: Check the module's required_providers for version constraints - Resolve: Update the module to a version compatible with the new provider - Lock file: Delete .terraform.lock.hcl and re-run terraform init - Upgrade: Use terraform init -upgrade to update provider versions
Common Distractors: - Editing .terraform.lock.hcl manually (wrong - regenerate with init) - Downgrading provider to match old module (wrong - better to update module) - Removing the module entirely (wrong - unnecessary, just version mismatch) - Using -force flag on init (wrong - no such flag exists)
Workspace and Environment Scenarios¶
Workspace-Based Environment Management¶
Scenario: You want to use Terraform workspaces to manage dev and prod environments with different instance sizes and counts.
Solution Pattern: - Workspaces: Create dev and prod workspaces - Variables: Use terraform.workspace to conditionally set values - Isolation: Each workspace maintains separate state
locals {
instance_type = terraform.workspace == "prod" ? "t3.large" : "t3.micro"
instance_count = terraform.workspace == "prod" ? 3 : 1
}
resource "aws_instance" "app" {
count = local.instance_count
ami = var.ami_id
instance_type = local.instance_type
tags = {
Environment = terraform.workspace
}
}
Common Distractors: - Using workspaces with Terraform Cloud (wrong - Cloud workspaces work differently than CLI workspaces) - Storing workspace state in the same file (wrong - each workspace has its own state) - Deleting the default workspace (wrong - default workspace cannot be deleted) - Using workspaces for entirely different infrastructure (wrong - intended for same config, different parameters)
Key Decision Factors¶
When to Use Which Approach¶
- Modules vs Workspaces: Modules for reusable components, workspaces for environment isolation
- Count vs For_each: Count for identical resources, for_each for resources with unique attributes
- Local vs Remote state: Local for individual work, remote for team collaboration
- Import block vs CLI import: Import block for bulk/declarative imports, CLI for one-off imports
- Provisioners vs native resources: Always prefer native provider resources over provisioners