All Modules Why IaC Concepts Hands-on Lab Cheat Sheet

Infrastructure as Code

Define your servers and services in code — repeatable, reviewable, version-controlled.

Module 10 · Terraform & Ansible. We provision the notes app from code. Free · Local.

Beginner+ IaC ~60 min

What You'll Learn

By the end of this module you will be able to:

  • Explain why clicking around in a console doesn't scale — and what IaC fixes
  • Tell the difference between provisioning (Terraform) and configuration (Ansible)
  • Write Terraform: provider, resource, variable, output, state
  • Run the Terraform loop: init → plan → apply → destroy
  • Write an Ansible playbook and understand idempotency
  • Recognize how both tools apply to real cloud servers, not just local ones

Prerequisites: Module 6 (Docker). We use Docker as a free, local stand-in for "infrastructure" so you need no cloud account.

Why IaC Exists

Imagine setting up a server by hand: click "create VM" in a web console, SSH in, install packages, edit configs. Now do it again for staging. And production. And again when it breaks at 2 a.m. Every server ends up slightly different — a "snowflake" no one can reproduce.

Click-ops doesn't scale

Manual setup is slow, error-prone, undocumented, and impossible to review or roll back. When the person who built it leaves, the knowledge leaves too.

Infrastructure as Code means describing your infrastructure in text files you commit to Git. The same benefits you get from application code now apply to your servers:

  • Repeatable — spin up identical environments in seconds
  • Versioned — every change is in Git history; roll back anytime
  • Reviewable — infra changes go through pull requests like code
  • Self-documenting — the code is the documentation

Two Jobs, Two Tools

IaC splits into two complementary tasks. Most teams use both.

TerraformAnsible
JobProvisioning — create the infra (servers, networks, DBs)Configuration — set up what runs on it (packages, files, services)
StyleDeclarative (describe the end state)Mostly declarative tasks, run top-to-bottom
LanguageHCL (HashiCorp Config Language)YAML playbooks
Tracks state?Yes — a state fileNo — checks reality each run
AnalogyBuilds the houseFurnishes the house

Declarative, like Kubernetes

You don't write steps ("create this, then that"). You describe the desired state and the tool figures out how to reach it — and what to change if reality drifts. Same mindset you learned with K8s manifests in Module 8.

Lab Part A: Provision with Terraform

Terraform talks to "providers" (AWS, Azure, GCP… and Docker). We'll use the Docker provider so you can provision the notes app on your own machine — the exact same skills transfer to a cloud provider, just by swapping the provider block. Make a new folder terraform/.

1

Install Terraform

Grab it from developer.hashicorp.com/terraform/install, then verify:

terraform -version
2

Describe the infrastructure — main.tf

This is the heart of the module. It declares a provider, a variable, two resources (the image + a running container), and an output. Read every block — we explain them next.

terraform { required_providers { docker = { source = "kreuzwerker/docker" version = "~> 3.0" } } } provider "docker" {} variable "app_port" { default = 8080 } resource "docker_image" "notes" { name = "notes-app:1.0" # the image you built in Module 6 } resource "docker_container" "notes" { name = "notes-tf" image = docker_image.notes.image_id ports { internal = 5000 external = var.app_port } } output "url" { value = "http://localhost:${var.app_port}" }

Need the image first

This references notes-app:1.0 from Module 6. If you don't have it, run docker build -t notes-app:1.0 . in your app folder first.

3

Initialize — download the provider

init reads your config and downloads the Docker provider plugin into the folder. Run it once per project (and after adding providers).

terraform init
4

Plan — preview the changes

This is Terraform's superpower: it shows exactly what it will create, change, or destroy before touching anything. Always read the plan.

terraform plan

You'll see + create for the image and container — a dry run with no surprises.

5

Apply — make it real

terraform apply # type 'yes' to confirm

Terraform creates the container and prints the url output. Open http://localhost:8080 — the notes app, provisioned entirely from code. ✅ Confirm with docker ps.

The "aha!" moment

You didn't run a single docker run. You described the desired state and Terraform built it. Swap the provider block for aws and the same workflow provisions real cloud servers.

6

Change something — see state in action

Edit main.tf and change default = 8080 to default = 9090, then plan again:

terraform plan # shows it will REPLACE the container (port changed) terraform apply # now on http://localhost:9090

What is "state"?

Terraform records what it created in a terraform.tfstate file. That's how it knows the difference between "what exists" and "what you want," and only changes the gap. Never edit state by hand, and never commit it if it holds secrets (add it to .gitignore; teams use remote state).

7

Destroy — clean up completely

One command tears down everything Terraform created — no leftovers, no forgotten resources running up a cloud bill.

terraform destroy # type 'yes'

Terraform Building Blocks

BlockWhat it does
terraform { }Settings — which providers and versions are required.
providerThe platform you're targeting (docker, aws, google…).
resourceA thing to create — an image, container, server, network.
variableAn input you can change without editing the core logic.
outputA value to print after apply (URLs, IPs, IDs).
terraform.tfstateAuto-generated record of what Terraform manages. Don't edit by hand.

References wire resources together

image = docker_image.notes.image_id means "use the image this other resource created." Terraform reads these references to figure out the correct order automatically — you never specify it.

Lab Part B: Configure with Ansible

Terraform built the box; now Ansible configures what's inside it — installing packages, writing config files, starting services. We'll run a playbook against your own machine (localhost) so there's nothing to provision. Make an ansible/ folder.

1

Install Ansible

pip install ansible ansible --version
2

Write a config template — app.conf.j2

Ansible uses Jinja2 templates so config can vary by environment. The {{ }} values are filled in from variables.

PORT={{ app_port }} ENVIRONMENT={{ app_env }}
3

Write the playbook — playbook.yml

A playbook is a list of tasks. Each task uses a module (file, template…) to bring the system to a desired state.

- name: Configure the notes app host hosts: localhost connection: local vars: app_port: 8080 app_env: development tasks: - name: Ensure config directory exists ansible.builtin.file: path: ~/notes-config state: directory - name: Render the app config from a template ansible.builtin.template: src: app.conf.j2 dest: ~/notes-config/app.conf
4

Run it — then run it again

ansible-playbook playbook.yml

Check the result: cat ~/notes-config/app.conf shows your rendered config. The first run reports changed=2. Now run the exact same command again:

ansible-playbook playbook.yml # this time: changed=0, ok=2

The "aha!" moment: idempotency

The second run changes nothing because the system is already in the desired state. This is idempotency — you can safely run a playbook any number of times and only the gaps get fixed. It's what makes config management trustworthy.

How This Maps to Real Servers

We used Docker and localhost to stay free — but the skills are identical for the cloud:

Today (local)In the cloud (same workflow)
provider "docker"provider "aws" / "google" / "azurerm"
docker_container resourceaws_instance (an EC2 server) resource
Ansible against localhostAnsible against an inventory of real servers over SSH
init → plan → applyExactly the same loop

The big takeaway

You've learned the workflow, which is the hard part. Targeting AWS instead of Docker is mostly a matter of credentials and different resource names — we do exactly that in the optional cloud bonus (Module 13).

Cheat Sheet

The commands you'll use every day. Bookmark this.

Terraform

CommandWhat it does
terraform initDownload providers, prep the folder
terraform fmtAuto-format your .tf files
terraform validateCheck the config for errors
terraform planPreview changes without applying
terraform applyCreate/update infrastructure
terraform destroyTear everything down
terraform showInspect current state
terraform outputPrint output values

Ansible

CommandWhat it does
ansible-playbook play.ymlRun a playbook
ansible-playbook play.yml --checkDry run (preview changes)
ansible-playbook play.yml -i hostsRun against an inventory file
ansible all -m pingTest connectivity to hosts
ansible-galaxy install <role>Install a reusable role

Troubleshooting

SymptomLikely cause & fix
Could not load plugin on initTypo in the provider source, or no internet. Re-check the required_providers block.
image not found on applyBuild it first: docker build -t notes-app:1.0 .
port already allocatedAnother container uses that port — change app_port and re-apply.
Terraform wants to destroy unexpectedlyState drifted (you changed things by hand). Read the plan carefully before saying yes.
Ansible YAML errorIndentation — 2 spaces, no tabs (same rule as Compose/K8s).
Playbook always shows changedA task isn't idempotent (e.g. raw command). Prefer purpose-built modules.

Your Challenge

Stretch your understanding before moving on:

  • Add a second Terraform variable for the container name and reference it with var..
  • Add a Postgres docker_container resource so Terraform provisions the whole stack (like Compose did).
  • Run terraform fmt and terraform validate on your config.
  • Bonus: add an Ansible task that fails if app_env isn't set, using ansible.builtin.assert.
variable "container_name" { default = "notes-tf" } # then inside the docker_container resource: name = var.container_name

Recap & What's Next

You can now

Provision infrastructure declaratively with Terraform (init/plan/apply/destroy + state), and configure systems idempotently with Ansible playbooks. Your environment is now as repeatable and reviewable as your code.

Next up: Module 11 — Monitoring & Logging, where you'll see what your app is actually doing in production with Prometheus and Grafana.

Infrastructure as Code

Objectives Why IaC Two Tools Lab A: Terraform TF Building Blocks Lab B: Ansible Maps to Cloud Cheat Sheet Troubleshooting Challenge Recap