Module 7 · Labs
These five labs turn your hand-built homelab into infrastructure as code. The two capstones are the payoff of the entire curriculum so far: rebuild your whole homelab from bare VMs with one command (Lab 3), and a documentation site that redeploys itself on every push (Lab 5). Commit everything (secrets excluded) to your self-hosted git server from Module 6.
Lab 1 · Idempotent script
Section titled “Lab 1 · Idempotent script”Goal: write automation that’s safe to run any number of times. Exercises Lesson 7.1.
- Write a bash script (with
set -euo pipefail) that sets up a new user, installs a few packages, and drops a config file into place. - Make every operation idempotent:
mkdir -p, “add line only if absent,” “create user only if missing,” etc. - Add a timestamped
log()function and a check of its assumptions before acting. - Prove idempotency: run it once (it does the work), then run it again and confirm the second run changes nothing and errors nowhere.
- Bonus: rewrite one piece that involves logic/JSON in Python, and note why it’s cleaner there.
Verify
Section titled “Verify”- The script uses strict mode and quotes its variables.
- Running it twice produces the same result; the second run is a no-op.
- You can point to each construct that makes an operation idempotent.
Commit
Section titled “Commit”automation/01-idempotent.sh plus a short note explaining what makes each step idempotent.
Lab 2 · Server as code
Section titled “Lab 2 · Server as code”This is the signature exercise. Reproduce your entire Module 2 server from an Ansible playbook. Exercises Lesson 7.2.
- Install Ansible on your workstation. Create an inventory with a fresh test VM.
- Write a playbook that encodes your entire Module 2 build: admin user, packages, and the full hardening checklist (SSH keys-only + no root, ufw default-deny + allow SSH, fail2ban, unattended-upgrades). Organize the hardening into a role.
- Run it against a wiped VM:
ansible-playbook -i inventory playbook.yml. In minutes you have a server identical to your hand-built one. - Prove idempotency: run it again; confirm every task reports
okandchanged=0. - Prove equivalence:
nmapthe Ansible-built server (Lesson 2.3) and confirm it’s hardened exactly like your hand-built one — only SSH open. - Time it vs. doing it by hand, and note the difference.
Verify
Section titled “Verify”- A fresh VM becomes a fully hardened server from one
ansible-playbookcommand. - A second run changes nothing (
changed=0) — verified idempotency. -
nmapconfirms the automated build matches your Module 2 hardening.
Commit
Section titled “Commit”ansible/ — your inventory, playbook, and hardening role (no secrets). This is the core of the
deliverable.
Lab 3 · Whole-lab rebuild
Section titled “Lab 3 · Whole-lab rebuild”A capstone. Extend Ansible to reconstruct your whole homelab, not just one server. Exercises Lessons 7.2 and 7.3.
- Extend your Ansible project to also deploy your Module 6 service stack — Docker, your Compose files, the reverse proxy config, the services.
- Aim for a single entry point:
ansible-playbook site.ymlprovisions bare VMs into your running homelab (hardened servers + services behind the proxy). - The proof: wipe your test VM(s) to a clean OS, run
site.yml, and watch your homelab reconstruct itself. Confirm services come up behind the proxy with TLS. - Record it (screen capture) — a bare VM becoming your homelab from one command.
Verify
Section titled “Verify”-
ansible-playbook site.ymlrebuilds hardened servers and the service stack from scratch. - You rebuilt onto genuinely fresh VMs, not a pre-configured one.
- You have a recording/log of the rebuild.
Commit
Section titled “Commit”Expand ansible/ with the service roles and site.yml. Reference your Module 6 Compose files.
Lab 4 · Secrets, safely
Section titled “Lab 4 · Secrets, safely”Goal: ensure no plaintext secret exists in any repo, and that deploys still work. Exercises Lesson 7.3.
- Audit your repos for any committed secrets (
gitleaks detect/trufflehog). Fix any found — and rotate anything that was exposed (assume it’s compromised). - Move every real secret out of plaintext: either sops/age (or Ansible Vault) encrypting a
committed
secrets.enc.yml, or gitignored.envfiles with committed.env.exampletemplates. - Wire your Ansible/Compose to decrypt/read secrets at deploy time; confirm a deploy still works end to end with the secret sourced securely.
- Add a
.gitignoreguard and (bonus) a pre-commitgitleakshook so a future mistake is blocked before it’s pushed. - Prove it: a teammate (or future you) can decrypt only with the right key; the repo alone leaks nothing.
Verify
Section titled “Verify”- A secret scanner reports no plaintext secrets in any repo.
- Secrets are encrypted-in-git or externalized; deploys still work.
- The decryption key lives outside git; the repo is useless to an attacker without it.
Commit
Section titled “Commit”Your encrypted secrets file and/or .env.example templates, plus the .gitignore/hook
config. Never the plaintext.
Lab 5 · The self-deploying site
Section titled “Lab 5 · The self-deploying site”This is the module’s deliverable. Build a CI/CD pipeline that redeploys your site automatically on every push. Exercises Lesson 7.4.
- Set up a self-hosted runner — Forgejo Actions on your Module 6 git server, or Woodpecker CI.
- Add a pipeline (
.forgejo/workflows/deploy.ymlor equivalent) that, on push tomain: checks out the repo, builds your site, and deploys it to where your reverse proxy serves it. - Add at least one gate that runs on pull requests: a lint, a build check, and/or a
gitleakssecret scan — and confirm a failing check blocks the merge. - The demo: push a small change (fix a typo), and watch the pipeline build and deploy it — the site updates live, untouched by hand.
- Confirm the full loop: code in your self-hosted git → pipeline on your runner → live site on your hardware.
Verify
Section titled “Verify”- A push to
mainautomatically builds and deploys your site — no manual steps. - A gate runs on PRs and blocks a failing change from merging.
- The entire pipeline runs on your own infrastructure.
Commit & record
Section titled “Commit & record”Commit the workflow file. Then record two short clips for your portfolio:
- a push → auto-deploy of the site, and
- the whole-lab rebuild from Lab 3 (bare VM → homelab via one command).
git add .forgejo/ ansible/ automation/git commit -m "Add CI/CD pipeline and infrastructure-as-code for the homelab"git push # ...which now triggers the deploy automaticallyModule 7 checkpoint
Section titled “Module 7 checkpoint”- I write idempotent automation and can explain why idempotency matters (Lab 1)
- My server’s entire configuration, including hardening, lives in an Ansible repo (Lab 2)
- I can rebuild my homelab from code onto fresh VMs (Lab 3)
- No plaintext secret exists in any of my repositories (Lab 4)
- A push to my docs repo deploys the site automatically, with no manual steps (Lab 5)
- I use pull requests and review my own changes before merge (Labs 3–5)
Deliverable
Section titled “Deliverable”An Ansible repo that rebuilds Modules 2–6 from scratch, plus a self-deploying-site pipeline — with recordings of a one-command homelab rebuild and a push-to-deploy. This is the most persuasive artifact in your portfolio: undeniable, demonstrable proof you can define, build, and operate infrastructure as code.
Next module: Module 8 · Security Operations → — where you attack your own lab and learn to find yourself in the logs.