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