5 exercises — Practice Terraform and IaC vocabulary in English: HCL block types, workflow, state management, modules, workspaces, and provider configuration.
A DevOps engineer introduces Terraform to a developer team: "Terraform is an Infrastructure-as-Code tool. Instead of clicking in the AWS console, you write HCL — HashiCorp Configuration Language — to declare what infrastructure should exist. A resource block declares a piece of infrastructure: an EC2 instance, an S3 bucket, a DNS record. Terraform computes a plan showing what will be created, changed, or destroyed, then applies it. The state file records what Terraform currently manages — it's the source of truth for your infrastructure." What is drift in the context of Terraform, and why is it dangerous?
Drift: the difference between what Terraform's state file says exists and what actually exists in the cloud. Causes: manual changes in the cloud console, another tool modifying infrastructure, resources changed by the cloud provider itself. Why dangerous: when you run terraform plan, Terraform diffs its state against your config — it does NOT automatically query the live cloud state. If someone added a security group rule manually, Terraform doesn't know. Next apply could remove it. terraform refresh (or terraform plan -refresh-only) syncs state from the live cloud, detecting drift. Terraform workflow vocabulary: terraform init: initialise working directory — downloads providers and modules. terraform validate: check syntax and configuration validity without accessing remote state or cloud. terraform plan: show what changes Terraform would make. Output: +create, ~update in-place, -/+ replace (destroy then create), -destroy. terraform apply: execute the plan. Prompts for confirmation unless -auto-approve. terraform destroy: destroy all managed infrastructure. Use with extreme caution in production. terraform fmt: reformat HCL to canonical style. terraform state list: list all resources in state. terraform import: import existing infrastructure into Terraform management. terraform taint (deprecated, use -replace): force recreation of a resource. In conversation: 'Our golden rule: nobody touches cloud resources manually. If you need to make a change, do it in Terraform. Drift is how we lose track of what we own.'
2 / 5
An infrastructure engineer explains Terraform state management: "By default, Terraform stores state locally in terraform.tfstate. That's fine for solo use but terrible for teams — two engineers running apply simultaneously can corrupt state. We use a remote backend: S3 for storage, DynamoDB for state locking. State locking means only one terraform apply can run at a time. Sensitive values in state — database passwords, API keys — are stored in plaintext in the state file. That's why state storage must be encrypted and access-controlled." What is state locking and why is it necessary for teams?
State locking: when terraform apply (or plan) starts, it acquires a lock on the state. Any other terraform operation that tries to acquire the lock will wait or fail with an error. Prevents race conditions: without locking, two concurrent applies could both read the same state, make conflicting changes, and write back inconsistent state. Remote backend options: S3 + DynamoDB: S3 stores state file, DynamoDB handles lock acquisition. Standard for AWS. GCS (Google Cloud Storage): built-in locking via object versioning. Terraform Cloud / HCP Terraform: managed state, locking, and run orchestration. Azure Blob Storage: built-in lease-based locking. State security: state files contain sensitive data in plaintext (database passwords, TLS private keys injected via resources). Required: encrypt at rest (S3 SSE), encrypt in transit (HTTPS), strict IAM access controls, audit logging. Never commit state files to git. Workspace vocabulary: Workspace: isolated state within a single configuration — useful for managing dev/staging/prod with the same code. terraform workspace new dev creates a new workspace. Separate state file per workspace. Alternative approach: separate directories/repos per environment (often preferred for larger orgs). In conversation: 'We lost an afternoon once to a corrupted state file from two engineers applying simultaneously. The DynamoDB lock table was the first thing we added the next morning.'
3 / 5
A senior engineer explains Terraform modules at a team training: "A module is a container for multiple resources used together. The root module is your main working directory. Child modules are called with module blocks — they can come from a local path, a git URL, or the Terraform registry. Modules take input variables and expose output values. The power: write once, reuse everywhere. We have a vpc-module, an eks-module, a rds-module. Every team uses them; changes propagate when you bump the module version." What is the difference between a Terraform variable and an output?
variable block: declares an input. Can have a type constraint, default value, description, validation block, sensitive flag. Receives values from: terraform.tfvars files, -var flags, environment variables (TF_VAR_name), interactive prompts. output block: declares a value to expose after apply. Usage: expose a child module's resource attributes to the parent; print useful values (like a load balancer DNS name) after apply; feed values between modules via terraform_remote_state. HCL block types: resource: declares a managed infrastructure object. data: reads existing infrastructure (queries cloud without creating anything). module: calls a child module. locals: defines local computed values (like variables but computed within the config). provider: configures a cloud provider (AWS, GCP, Azure, etc.). terraform: backend configuration and required_providers. Meta-arguments: for_each: creates multiple instances from a map or set. Better than count for most use cases. count: creates N instances. Fragile with removals (removes by index). depends_on: explicit dependency when implicit dependencies aren't enough. lifecycle.create_before_destroy: creates replacement before destroying old resource — avoids downtime. lifecycle.prevent_destroy: prevents accidental destruction of critical resources. In conversation: 'When a module requires 20 variables, that's a code smell — split it into smaller modules with clearer responsibilities.'
4 / 5
A DevOps lead reviews a Terraform configuration issue: "The team had a resource that needed to reference an attribute from another resource not yet created. Terraform normally handles implicit dependencies — if resource B references resource A.id, Terraform knows to create A first. But this case needed an explicit depends_on because the dependency was on a side effect, not a referenced attribute. Another issue: they were using count to create 5 subnets. When they removed subnet 3, Terraform renumbered and wanted to destroy/recreate subnets 3 and 4. We switched to for_each with a map — removals are now by key, not index." Why is for_each generally preferred over count for creating multiple resources?
count pitfall: resources created with count are addressed as resource.name[0], resource.name[1], etc. If you remove item at index 2 from a list of 5, Terraform sees that resource.name[2] should now be the old [3], [3] should be [4], and [4] is gone. It plans: destroy [2], update [3] (renaming to [2]), destroy [4], update [5-1]... Potentially destroys and recreates production resources unnecessarily. for_each advantage: resources are addressed by key (resource.name["subnet-a"]). Removing "subnet-c" only affects that one resource. Other resources are unaffected. for_each types: map(any): iterate over key-value pairs. set(string): iterate over a set of strings (no associated value). Example: for_each = toset(["us-east-1a", "us-east-1b", "us-east-1c"]) — creates three subnets with stable keys. Terraform best practices vocabulary: Implicit dependency: Terraform infers from attribute references (aws_instance.web.id → Terraform knows to create aws_instance.web first). Explicit dependency (depends_on): manual declaration of dependency when not captured by attribute reference. Use sparingly. Resource addressing: how Terraform uniquely identifies a resource in state. module.vpc.aws_subnet.private["az-a"] — module path + resource type + name + key. State migration: when you rename a resource, Terraform plans to destroy old + create new. Use terraform state mv to rename without touching infrastructure. In conversation: 'Our rule of thumb: always use for_each over count unless you truly have N identical, interchangeable resources. In practice that's almost never — subnets, instances, users all have distinct identities.'
5 / 5
An engineer discusses remote state data sources: "We have three Terraform repositories: network, platform, apps. Apps needs the VPC ID and subnet IDs from network. We use terraform_remote_state data source — it reads the outputs from another state file. Apps' Terraform reads the network state bucket and gets the VPC ID directly. This way network and apps are separate deployment units but apps can consume network's outputs without hardcoding IDs." What is a data source in Terraform and how does it differ from a resource?
data source: read-only query of existing infrastructure or external data. Terraform fetches it during plan/apply but never creates, modifies, or destroys it. Examples: data.aws_ami.ubuntu (latest Ubuntu AMI ID), data.aws_vpc.main (existing VPC attributes), data.terraform_remote_state.network (outputs from another Terraform state), data.aws_secretsmanager_secret_version.db_password (secret value). resource: Terraform fully manages. Creates on first apply, updates if config changes, destroys if removed from config. Data source vocabulary: terraform_remote_state: reads outputs from another Terraform state file stored in a remote backend. Enables loose coupling between stacks. Sensitive output: outputs marked sensitive = true are redacted in plan/apply output but available in state. Backend configuration: the terraform block specifying where state is stored. Cannot be parameterised with variables (Terraform processes it before variables are resolved). Solution: use partial backend configuration + -backend-config flags or environment variables. IaC vocabulary: Idempotent: running terraform apply multiple times with no changes produces no further changes. Declarative: you declare the desired end state, not the steps to get there (vs. imperative scripts). Immutable infrastructure: replace rather than update resources — destroy old, create new. Terraform encourages this with replace. In conversation: 'Data sources are how we avoid hardcoding IDs. AMI IDs change across regions and over time — always look them up with a data source rather than embedding them.'