Hands-on Exercises — Build with Your Own Hands
The builder didn't learn by reading about blueprints — they built a villa. These exercises get you running real Terraform against real AWS. Everything uses S3 buckets, which cost nothing while empty, but always run terraform destroy when you finish.
Before You Start
- Install Terraform — download the binary for your OS from terraform.io/downloads and add it to your PATH. Verify with
terraform version. - An AWS account — create one at aws.amazon.com (free tier is fine).
- Credentials — create an access key in the IAM console, then export it:
export AWS_ACCESS_KEY_ID="AKIA..."
export AWS_SECRET_ACCESS_KEY="..."
- A region — we'll use
eu-west-1.
Credentials are secrets. Never commit them, never share them. On a real team, you'd use IAM roles or a tool like aws-vault instead of exported keys.
Exercise 0 · Your First Blueprint
mkdir campus-library-infra && cd campus-library-infra
Create main.tf:
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = "eu-west-1"
}
resource "aws_s3_bucket" "archive" {
bucket = "campus-library-${random_string.suffix.result}"
tags = {
Project = "Campus Library"
}
}
resource "random_string" "suffix" {
length = 6
special = false
}
S3 bucket names are globally unique, so the random_string gives you a guaranteed-available name.
Run the ritual:
terraform init # download providers
terraform fmt # tidy the files
terraform validate # check for errors
The random provider needs no cloud credentials — but the aws provider does. If init succeeds but plan complains about credentials, check Exercise 0's setup steps.
Exercise 1 · Preview and Build
terraform plan
Expected output:
Plan: 2 to add, 0 to change, 0 to destroy.
Then build it:
terraform apply
Type yes when prompted. Expected:
Apply complete! Resources: 2 added, 0 changed, 0 destroyed.
Checkpoint: look in the AWS console → S3 → you should see your bucket. Terraform just built infrastructure from a blueprint.
Exercise 2 · Variables and Outputs
Create variables.tf:
variable "project" {
type = string
default = "Campus Library"
description = "Project name used in tags"
}
variable "region" {
type = string
default = "eu-west-1"
}
Create outputs.tf:
output "bucket_name" {
value = aws_s3_bucket.archive.bucket
}
output "bucket_arn" {
value = aws_s3_bucket.archive.arn
}
Point the provider at the variable and re-apply:
provider "aws" {
region = var.region
}
terraform apply
terraform output
bucket_name = "campus-library-a1b2c3"
bucket_arn = "arn:aws:s3:::campus-library-a1b2c3"
Exercise 3 · Change a Resource — Watch the Plan
Edit the tags in main.tf to add a new tag:
tags = {
Project = "Campus Library"
ManagedBy = "Terraform"
}
Now look at what Terraform says:
terraform plan
Plan: 0 to add, 1 to change, 0 to destroy.
Apply it. Terraform detected the difference between blueprint and reality, and made a tiny update — that's the state register doing its job.
Exercise 4 · Inspect the State
terraform state list
terraform state show aws_s3_bucket.archive
Try the interactive shell:
terraform console
> aws_s3_bucket.archive.bucket
> length(var.project)
> exit
state show prints the real attributes of what you built. Interviewers love hearing you mention state inspection naturally.
Exercise 5 · Demolition
terraform destroy
Type yes. Confirm in the AWS console that the bucket is gone.
Checkpoint: verify with terraform state list — the register is now empty.
Recap Checklist
- terraform init / fmt / validate
- terraform plan before apply
- Created a real AWS bucket
- Variables + outputs declared
- Changed a resource and re-applied
- Inspected state list / state show
- Destroyed everything cleanly
Then challenge yourself with the Capstone — Build the Colony.
Check Your Understanding
1 · What command downloads provider plugins?
terraform init — it sets up the working directory and installs the providers declared in required_providers. Run it after any provider change.
2 · Which command shows changes but changes nothing?
terraform plan — it compares blueprint against state and prints the diff, with zero side effects.
3 · What is the difference between a resource and a data source?
A resource is created and managed by Terraform. A data source is read-only — it fetches information about existing infrastructure.
4 · Why are S3 bucket names combined with random_string in these exercises?
S3 bucket names must be globally unique across all AWS accounts. The random suffix guarantees the name isn't already taken.
5 · What does a plan that says "0 to add, 0 to change, 0 to destroy" mean?
The blueprint and reality already match — nothing to do. This is the expected idle state.
6 · Where does state live by default, and where should it live for a team?
By default in a local terraform.tfstate file. For a team it should live in a remote backend (e.g., S3 with DynamoDB locking) so everyone shares one register.
7 · What is drift, and which command fixes it?
Drift is when live infrastructure differs from state (someone changed it outside Terraform). terraform apply -refresh-only updates state to match reality without changing anything.
8 · Why are credentials never committed to the repo?
They're secrets. Committed keys get leaked and abused — AWS keys found in public repos are routinely hijacked to mine crypto or run expensive infrastructure.
9 · What does the terraform.lock.hcl file do?
It pins the exact provider versions used, so every team member (and CI) uses identical provider code — reproducible builds.
10 · What does terraform state rm do, and why is it risky?
It stops Terraform from tracking a resource — but leaves it running in the cloud. The resource becomes orphaned (unmanaged). Use import to bring resources into state instead of abandoning them.
Next up: the Check-Your-Understanding answers are right above — then move to the Capstone and the Glossary.