Modularity and State Workshop
Kata: Take Lab 1's single-file module and refactor it into a module boundary, then back it with remote state and state locking. This exercises Cluster 3 -- module contracts, remote state, and team safety.
Retrieval Prompts
- What distinguishes a reusable module from a root module?
- Name three things that should not be inside a reusable module (and belong in the root / composition layer instead).
- What is a
backendblock, and what doesterraform init -migrate-statedo? - What is "state locking," mechanically -- who holds the lock, where, and for how long?
- Why are workspaces for environments discouraged in production-shaped orgs?
Compare and Distinguish
- Module input contract vs provider configuration. A reusable module should not configure providers inline. Why?
terraform workspace new prodvs a separateenvs/prod/directory. Which is the norm and why?- Local state on your laptop vs S3 + DynamoDB remote state. What changes about the failure modes?
- Polyrepo (repo per service) vs monorepo (all infra in one). What does each cost when the company grows from 5 to 50 engineers?
The Workshop
- Extract the module. Move your Lab 1 code into
modules/storage-bucket/. Remove anyproviderblock from the module -- providers must be configured in the root only. - Compose two environments. Create
envs/dev/andenvs/prod/directories. Each calls the module with differentnameandenvironmentinputs. - Add remote state. Create an S3 bucket (or the GCS / Azure equivalent) and a DynamoDB lock table by hand once. Then wire a
backend "s3"block per environment withdynamodb_tablelocking. - Migrate. Run
terraform init -migrate-stateto move local state into the backend. Verify by runningterraform state listagainst the backend. - Prove locking. In two terminals, run
terraform planagainstenvs/prod/simultaneously. Capture the lock error one of them receives. - Break state deliberately. In a scratch environment only, delete one resource from state with
terraform state rm. Runplan. Observe what Terraform now believes (and what reality holds). Recover withterraform import.
Mini Application
Write a modules/storage-bucket/README.md that includes:
- a one-sentence purpose statement
- a Usage block with a
modulecall example using a versioned source - an Inputs table (variable name, type, default, required, description)
- an Outputs table
- a Design notes paragraph answering: what is this module responsible for, and what is it explicitly not responsible for?
Common Mistake Check
Identify and fix:
- A reusable module contains
provider "aws" { region = "us-east-1" }. envs/prod/main.tfandenvs/dev/main.tfeach have a different set of inputs to the module -- but one of them silently has a different resource schema.- Two engineers committed
terraform.tfstateto git "so both could use it." - The DynamoDB lock table is
acme-tf-locks, but the backend config forenvs/prod/was copy-pasted fromenvs/dev/and still points atacme-tf-locks-dev. Why is this dangerous? terraform workspace new prodis used as the prod environment. What operator surprise is likely in the first year?
Evidence Check
This page is complete only when you can:
- show a
modules/+envs/dev/+envs/prod/layout that applies cleanly - produce the two-terminal lock error in a screenshot
- explain, in 60 seconds, how S3+DynamoDB locking works mechanically
- recover from a
terraform state rmusingterraform import - justify why your module boundary is where it is (not somewhere else)