Skip to main content

First Terraform Module Lab

Kata: Write, review, and apply a single-resource Terraform module end to end, with plan output you can defend. The goal is fluency with Cluster 1 and Cluster 2 concepts -- declarative thinking, state, providers/resources/variables, and the plan/apply lifecycle.

Target infrastructure (any of these is fine; pick one):

  • an S3 bucket with encryption, versioning, and public access blocked
  • an Azure Storage Account with TLS 1.2 and a single container
  • a Google Cloud Storage bucket with uniform bucket-level access

Use a free-tier account or LocalStack. Destroy at the end.

Retrieval Prompts

  1. State from memory the job of terraform init, plan, and apply in one sentence each.
  2. What is the difference between a provider block, a resource block, and a data block?
  3. What does the ~> version constraint mean, and why is required_version separate from provider versions?
  4. What lives in state but not in code? What lives in code but not in state?
  5. What is the difference between a ~, a -/+, and a + in plan output?

Compare and Distinguish

Before you touch code, explain the difference between:

  • declarative (this lab) and imperative (a Bash script that calls aws s3 mb)
  • variable, locals, and output -- what each is for; where each appears
  • state (Terraform's map of config -> real resources) and the real world (the AWS console)
  • terraform destroy and manually deleting the bucket from the console

You should be able to answer each in two sentences without looking anything up.

The Lab

Structure your module like this:

first-module/
main.tf
variables.tf
outputs.tf
versions.tf
README.md
  1. versions.tf: pin terraform version and the provider. Use required_providers.
  2. variables.tf: define name (string, required), environment (string, one of dev/staging/prod, validated), tags (map, default {}).
  3. main.tf: create the target resource. Apply tags merged with Env = var.environment and ManagedBy = "terraform".
  4. outputs.tf: export the resource id/arn and the bucket name (or equivalent).
  5. README.md: one-paragraph summary, plus a table of inputs and outputs.

Then run the local validation path in order:

terraform init
terraform fmt
terraform validate
terraform plan -out=tfplan
terraform show -no-color tfplan > plan.txt

If this is a cloud sandbox exercise and budget alerts plus a teardown plan already exist, continue with:

terraform apply tfplan
terraform show
terraform destroy

Capture plan.txt in all cases. If you stay local-first, also capture a mock resource diagram and a static-scan result instead of applying. If you use a cloud sandbox, capture the final terraform show output and the teardown evidence after terraform destroy.

Common Mistake Check

Identify and fix the error in each:

  1. terraform apply run without a saved plan in a team setting. (what guarantee is lost?)
  2. provider "aws" { region = "us-east-1" } repeated in every module. (what belongs at the root vs inside a module?)
  3. variable "env" { type = string } with no validation. (what bug does this invite?)
  4. resource "aws_s3_bucket" "b" { bucket = "logs" } with no tags. (what breaks first in an org with cost attribution?)
  5. terraform destroy run to "clean up" a prod environment. (what you should do instead, and why.)

Evidence Check

This page is complete only when you can:

  • produce the plan.txt and show where the + actions appear
  • articulate, for each file (main.tf, variables.tf, outputs.tf, versions.tf), the one thing it is responsible for
  • explain what would happen to the terraform.tfstate file during an apply, or what did happen if you used the cloud sandbox, and where it lives now
  • defend every attribute you set (not "the docs had it"; what the attribute does)
  • run terraform destroy without hesitation in a sandbox because your state matches reality, or explain why the local-first path intentionally stopped at validation/plan review