Skip to main content

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​

  1. Install Terraform — download the binary for your OS from terraform.io/downloads and add it to your PATH. Verify with terraform version.
  2. An AWS account — create one at aws.amazon.com (free tier is fine).
  3. Credentials — create an access key in the IAM console, then export it:
export AWS_ACCESS_KEY_ID="AKIA..."
export AWS_SECRET_ACCESS_KEY="..."
  1. A region — we'll use eu-west-1.
Careful

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:

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
Remember

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:

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:

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
Remember

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.