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
- State from memory the job of
terraform init,plan, andapplyin one sentence each. - What is the difference between a
providerblock, aresourceblock, and adatablock? - What does the
~>version constraint mean, and why isrequired_versionseparate from provider versions? - What lives in state but not in code? What lives in code but not in state?
- 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, andoutput-- what each is for; where each appearsstate(Terraform's map of config -> real resources) and the real world (the AWS console)terraform destroyand 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
versions.tf: pinterraformversion and the provider. Userequired_providers.variables.tf: definename(string, required),environment(string, one ofdev/staging/prod, validated),tags(map, default{}).main.tf: create the target resource. Apply tags merged withEnv = var.environmentandManagedBy = "terraform".outputs.tf: export the resource id/arn and the bucket name (or equivalent).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:
terraform applyrun without a saved plan in a team setting. (what guarantee is lost?)provider "aws" { region = "us-east-1" }repeated in every module. (what belongs at the root vs inside a module?)variable "env" { type = string }with novalidation. (what bug does this invite?)resource "aws_s3_bucket" "b" { bucket = "logs" }with no tags. (what breaks first in an org with cost attribution?)terraform destroyrun 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.txtand 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.tfstatefile 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 destroywithout hesitation in a sandbox because your state matches reality, or explain why the local-first path intentionally stopped at validation/plan review