Deep-dive FAQ for the Terraform Associate (003) exam: format & scope, versions and tooling, workflow/state/backends, variables/modules, providers/auth, import/replace/drift, workspaces vs separate state, CI/CD, policy-as-code, and exam-day tips.
Core Terraform fundamentals: the workflow (init → plan → apply → destroy
), state & backends, variables/outputs/locals, providers & auth, modules & versioning, workspaces, import/replace/refresh-only, basic policy-awareness, and safe practices (review plans, least privilege, secrets handling). It’s tooling- and vendor-neutral (AWS/Azure/GCP examples appear, but you’re tested on Terraform behavior).
Cloud/DevOps/platform engineers, SREs, and developers who write or review Terraform. If you can read plans, reason about state/backends, and build small modules safely, you’re ready to prep.
Expect mainstream Terraform 1.x behaviors and terminology used in current docs. Pin a recent 1.x locally and avoid legacy mental models (e.g., prefer -replace
over tainting).
Multiple-choice/single- and multi-select questions (with realistic code snippets and CLI scenarios). CompTIA-style PBQs are not the norm; still, be ready to interpret small HCL fragments and plans quickly.
Typical: 2–4 weeks with short labs. Daily cadence:
Memorize command shapes and var precedence; understand why we prefer for_each
over count
for stable addressing, when to use workspaces vs separate state, and how backends/locking prevent collisions.
1terraform init
2terraform fmt -recursive
3terraform validate
4terraform plan
5terraform apply
6terraform plan -destroy
7terraform destroy
Know when to add flags (e.g., -var-file
, -out
, -replace
, -refresh-only
).
State maps config ↔ real resources; keep it secure and backed up.
Remote backends centralize state and enable locking:
azurerm
(blob storage)gcs
Initialize or reconfigure with terraform init
and -backend-config
flags.
Never hand-edit state unless you’re performing a surgical recovery.
dev
, stage
, prod
). Good for light envs that share topology.count
and for_each
?count
→ index-based (resource[0]
), can reshuffle addresses when list order changes.for_each
→ key-based (map/set), stable addressing, fewer surprises on updates.
Prefer for_each
for collections with meaningful keys.Example
1resource "aws_iam_user" "user" {
2 for_each = toset(["alice", "bob"])
3 name = each.key
4}
1terraform {
2 required_version = "~> 1.8"
3 required_providers {
4 aws = { source = "hashicorp/aws", version = "~> 5.0" }
5 }
6}
7module "vpc" {
8 source = "terraform-aws-modules/vpc/aws"
9 version = "~> 5.0"
10}
Commit .terraform.lock.hcl
to keep provider versions consistent across devs/CI.
sensitive = true
(prevents UI echo).Two flows:
A) Import blocks (preferred when supported)
1resource "aws_s3_bucket" "logs" { bucket = "my-logs" }
2
3import {
4 to = aws_s3_bucket.logs
5 id = "my-logs"
6}
1terraform plan
2terraform apply
B) Legacy CLI
1terraform import aws_s3_bucket.logs my-logs
Then add matching HCL if it wasn’t present and apply.
-replace
?When you intend to recreate a resource (e.g., pet VM with drift, or immutable replacement):
1terraform plan -replace=aws_instance.app
2terraform apply -replace=aws_instance.app
Prefer this over the old taint workflow.
-refresh-only
, and when do I use it?To detect/report external drift without proposing changes:
1terraform plan -refresh-only
2terraform apply -refresh-only
Great for reconciling state with reality before deciding your next step.
-target
safe?Use sparingly. It can bypass the intended DAG and leave infra half-updated. Prefer architectural fixes or staged applies over frequent targeting.
1terraform plan -out=plan.bin
2terraform show plan.bin
3terraform apply plan.bin
fmt
/validate
and plan on PR; gate apply
on approval.-var-file
and consider separate states/backends per environment.Awareness helps: remote runs, remote state, workspaces, variables, and policy-as-code (Sentinel). You should recognize the concepts even if you don’t use them daily.
Recognize that policies evaluate plans/applies for compliance (tagging, regions, cost, security). Sentinel is native to Terraform Cloud/Enterprise; OPA/Rego is a common ecosystem alternative.
AWS
1provider "aws" {
2 region = var.region
3 profile = var.aws_profile
4}
Env vars: AWS_ACCESS_KEY_ID
, AWS_SECRET_ACCESS_KEY
, AWS_SESSION_TOKEN
, AWS_PROFILE
.
Azure
1provider "azurerm" { features {} }
Use az login
or a service principal: ARM_CLIENT_ID
, ARM_CLIENT_SECRET
, ARM_TENANT_ID
, ARM_SUBSCRIPTION_ID
.
GCP
1provider "google" {
2 project = var.project
3 region = var.region
4}
ADC via gcloud auth application-default login
or GOOGLE_APPLICATION_CREDENTIALS
.
Highest → lowest:
-var
/ later -var-file
*.auto.tfvars
(alphabetical)terraform.tfvars
/ .tfvars.json
TF_VAR_name
default
Common causes: changing keys with for_each
, lack of stable identifiers, provider bugs, or lifecycle changes. Use stable keys, consider lifecycle.ignore_changes
for externally managed fields, and pin versions.
create_before_destroy
?When a resource must exist before replacing the old (e.g., load balancers, launch templates):
1lifecycle {
2 create_before_destroy = true
3}
Pair with outputs and depends-on as needed to keep traffic flowing.
Prefer cloud-init/user-data or proper config mgmt. Provisioners are last resort (network reachability, idempotency risks).
Break into small, composable modules with good READMEs and version pinning. Keep env-specific values in var files and use outputs for wiring across modules.
for_each
over count
for stable addressing; -replace
for intentional recreation; -refresh-only
for drift checks; use -target
sparingly.sensitive
, locals, and outputs.for_each
vs count
and avoid address churn.-replace
.-refresh-only
and know when not to use -target
.