HashiCorp Terraform Associate (003) — Cheatsheet

High-yield Terraform review for the Associate (003) exam: core workflow, state/backends/locking, variables & expressions, modules & versioning, providers & auth, import/replace/refresh-only, workspaces, policy & best practices, and common CLI.

Use this for last-mile review. Skim top-to-bottom, star weak rows, and pair with targeted drills.


1) Core workflow & files

Standard loop: init → fmt/validate → plan → apply → (plan -destroy) → destroy

1terraform init            # download providers/modules, set backend
2terraform fmt -recursive  # canonical formatting
3terraform validate        # static checks
4terraform plan            # create execution plan
5terraform apply           # apply plan (auto-approve optional)
6terraform plan -destroy   # plan a destroy
7terraform destroy         # tear down

Key files

  • main.tf / *.tf — resources, data, providers, modules
  • variables.tf — input vars + types/validation
  • outputs.tf — outputs (can be sensitive = true)
  • providers.tfrequired_providers, auth/config
  • versions.tfrequired_version, provider constraints
  • .terraform.lock.hcl — provider dependency lock
  • terraform.tfvars / *.auto.tfvars — default var values
  • .tfvars.json — JSON var files
  • .terraform/ — working dir (providers/modules cache)

Provider & CLI version constraints

1terraform {
2  required_version = "~> 1.8"         # allow patch/minor within 1.8.x
3  required_providers {
4    aws = { source = "hashicorp/aws", version = "~> 5.0" }
5  }
6}

2) Providers & authentication (multi-cloud patterns)

AWS

  • Env: AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_SESSION_TOKEN, AWS_PROFILE
  • Shared config/creds files; SSO profiles supported
1provider "aws" {
2  region  = var.region
3  profile = var.aws_profile
4}

Azure

  • az login / Service principal (ARM_CLIENT_ID, ARM_CLIENT_SECRET, ARM_TENANT_ID, ARM_SUBSCRIPTION_ID)
1provider "azurerm" { features {} }

GCP

  • GOOGLE_APPLICATION_CREDENTIALS (JSON), or ADC via gcloud auth application-default login
1provider "google" { project = var.project  region = var.region }

Multiple providers / aliases

1provider "aws" { region = "us-east-1" }
2provider "aws" { alias  = "west" region = "us-west-2" }
3
4resource "aws_s3_bucket" "logs" {
5  provider = aws.west
6  bucket   = "my-logs-west"
7}

3) Variables, validation, locals, outputs

Input variables (types + validation + sensitive)

 1variable "instance_type" {
 2  type        = string
 3  default     = "t3.micro"
 4  validation {
 5    condition     = contains(["t3.micro","t3.small"], var.instance_type)
 6    error_message = "Use t3.micro or t3.small."
 7  }
 8}
 9
10variable "db_password" {
11  type      = string
12  sensitive = true
13}

Var precedence (highest → lowest)

  1. -var and later -var-file on CLI
  2. *.auto.tfvars (alphabetical)
  3. terraform.tfvars / .tfvars.json
  4. Environment TF_VAR_name
  5. Variable default

Locals (computed values)

1locals {
2  common_tags = { app = "shop", env = var.env }
3}

Outputs

1output "alb_dns" {
2  value     = aws_lb.app.dns_name
3  sensitive = false
4}

4) Expressions, collections & loops

Count vs for_each

  • count → index-based, good for homogeneous lists
  • for_each → key-based (map/set), stable addressing, preferred
1resource "aws_iam_user" "basic" {
2  for_each = toset(["alice","bob"])
3  name     = each.key
4}

for / dynamic blocks

 1tags = { for k, v in local.common_tags : k => v if v != "" }
 2
 3dynamic "ingress" {
 4  for_each = var.ingress_rules
 5  content {
 6    from_port   = ingress.value.port
 7    to_port     = ingress.value.port
 8    cidr_blocks = ingress.value.cidr_blocks
 9  }
10}

Useful functions (memorize)

  • coalesce(), try(), can()
  • merge(), concat(), zipmap(), toset(), tomap()
  • regex(), regexall(), replace()
  • cidrsubnet(), cidrhost()
  • jsonencode(), yamldecode()
  • file(), templatefile()

5) Resource graph, dependencies & lifecycle

Implicit deps via references: resource A uses resource B’s attr → DAG edge.

Explicit deps (use sparingly)

1resource "aws_instance" "app" {
2  depends_on = [aws_iam_role.app]
3}

Lifecycle meta-arguments

1resource "aws_launch_template" "lt" {
2  lifecycle {
3    create_before_destroy = true   # minimize downtime for replace
4    prevent_destroy       = true   # guard rails
5    ignore_changes        = [tags] # drift tolerance for fields
6    replace_triggered_by  = [var.ami_id] # re-create when input changes
7  }
8}

Provisioners — last resort only; prefer cloud-init/user-data or config mgmt.


6) Modules & versioning (best practices)

Layout

1modules/
2  vpc/
3    main.tf variables.tf outputs.tf README.md
4envs/
5  prod/ main.tf

Call modules

1module "vpc" {
2  source  = "terraform-aws-modules/vpc/aws"
3  version = "~> 5.0"
4  name    = "core-vpc"
5  cidr    = "10.0.0.0/16"
6}

Pin versions for modules & providers. Keep modules small, composable, documented. Prefer input validation, typed variables, and outputs for wiring.


7) State, backends & locking

State facts

  • Maps configs ↔ real resources
  • Sensitive — protect and back up
  • Do not edit manually (unless you know what you’re doing)

Remote backends (common)

  • S3 + DynamoDB (locking):
1terraform {
2  backend "s3" {
3    bucket         = "tf-state-prod"
4    key            = "network/terraform.tfstate"
5    region         = "us-east-1"
6    dynamodb_table = "tf-locks"
7    encrypt        = true
8  }
9}
  • azurerm (Storage Account + blob)
  • gcs (GCS bucket)

Init with partial config

1terraform init \
2  -backend-config="bucket=tf-state-prod" \
3  -backend-config="key=apps/prod.tfstate"

State CLI (surgical fixes)

1terraform state list
2terraform state show aws_s3_bucket.logs
3terraform state mv  aws_s3_bucket.old  aws_s3_bucket.new
4terraform state rm  aws_s3_bucket.orphan
5terraform providers                     # see dependencies
6terraform providers mirror ./providers  # air-gapped

Lock file: .terraform.lock.hcl pin provider versions; commit it.


8) Import, replace & refresh-only (modern flows)

Import (two options)

  1. Import blocks (preferred when supported):
1# In config:
2resource "aws_s3_bucket" "logs" { bucket = "my-logs" }
3
4import {
5  to = aws_s3_bucket.logs
6  id = "my-logs"
7}

Then:

1terraform plan
2terraform apply
  1. Legacy CLI import:
1terraform import aws_s3_bucket.logs my-logs

Replace resource intentionally (instead of taint)

1terraform plan  -replace=aws_instance.app
2terraform apply -replace=aws_instance.app

Refresh-only change detection

1terraform plan  -refresh-only
2terraform apply -refresh-only

9) Workspaces (when/how)

  • Isolate state instances within the same config (e.g., default, dev, stage, prod)
  • Good for lightweight envs; for strict isolation, prefer separate states (and often separate backends)
1terraform workspace list
2terraform workspace new stage
3terraform workspace select prod

Use in config

1locals {
2  name_suffix = terraform.workspace == "prod" ? "" : "-${terraform.workspace}"
3}

Pitfall: Workspaces do not branch provider credentials or networking per se; you must still parameterize those.


10) Data sources & outputs across modules

Data source (read-only view of existing infra)

1data "aws_ami" "latest" {
2  most_recent = true
3  owners      = ["amazon"]
4  filter {
5    name   = "name"
6    values = ["amzn2-ami-hvm-*-x86_64-gp2"]
7  }
8}

Expose values from modules

1output "subnet_ids" {
2  value = module.vpc.private_subnet_ids
3}

11) Resource addressing & selectors

  • Index with count: aws_instance.web[0]
  • Keys with for_each: aws_security_group.sg["admin"]
  • Module address: module.vpc.aws_subnet.this[each.key]

For loops for maps/lists

1variable "names" { type = list(string) }
2locals {
3  name_tags = { for n in var.names : n => { Name = n } }
4}

12) Policy & governance (awareness)

  • Sentinel (Terraform Cloud/Enterprise): policy as code on plans/applies
  • OPA/Rego (ecosystem): guardrails via CI or custom tooling
  • Registry module quality: verified badges, version pinning, READMEs, examples

Golden rules

  • Version-pin providers/modules
  • Code review every plan (terraform plan artifacts)
  • Principle of least privilege on cloud creds
  • Keep secrets out of state (use data sources, external systems, or sensitive + providers’ secrets managers)

13) Common exam “pickers” (decision heuristics)

  • Drift detected → run plan -refresh-only to reconcile; then decide remediation.
  • One resource must be recreated on changelifecycle.create_before_destroy (or replace_triggered_by) instead of manual ordering.
  • Need a one-off re-create-replace=addr (do not hand-edit state).
  • Import existing infra → add HCL resource + import block (or terraform import), then plan/apply.
  • Parallel envs → prefer separate states/backends; use workspaces for simple variants.
  • Large fan-out (stable keys) → for_each (avoid index churn from count).
  • Allow external change on a fieldlifecycle.ignore_changes = [field].
  • Prevent accidental deletionlifecycle.prevent_destroy = true (with break-glass procedure).
  • Multi-region → provider aliases and explicit selection per resource/module.

14) Frequently used CLI flags

1terraform plan   -var-file=prod.tfvars -out=plan.bin
2terraform apply  plan.bin
3terraform plan   -target=module.network  # surgical, use sparingly
4terraform plan   -replace=aws_lb.app
5TF_LOG=INFO terraform apply              # debug logging (temporary)

Use -target sparingly (can violate the DAG intent). Prefer architectural fixes or staged applies.


15) Troubleshooting cues

  • Provider install/auth errors → check required_providers, network/proxy, and env creds.
  • Address moved/renamedterraform state mv old new.
  • Unknown changes every plan → check ignore_changes, external controllers, or missing inputs; ensure provider versions match across machines/CI (lock file!).
  • Workspace confusionterraform workspace show and parameterize names/regions.

16) Tiny lab template (10-minute starter)

  1. New dir; create versions.tf, providers.tf, main.tf, variables.tf.
  2. Configure S3 backend (or local if just learning).
  3. One module call (e.g., VPC) + one resource (e.g., an S3 bucket).
  4. Add outputs.tf (expose IDs/ARNs).
  5. Run: init → fmt → validate → plan → apply.
  6. Practice: -replace, import block, plan -refresh-only, and workspaces.

Pair with…

  • Syllabus: objectives & quick links → open
  • Practice: timed drills & mocks → start
  • Overview: format & 2–4 week plan → read