Welcome & the DevOps landscape
"But it works on my machine."
A practical control room for learning how technical work moves from a checked change to a reliable result.
Four chapters. One observable habit.
The authenticated Maru items are paired with the original slides and practice briefs, so every task has a traceable source.
"But it works on my machine."
Git fundamentals
Lecture 3: Containers 101 — Why & How
Lecture 4: Images, Layers & Multi-Stage Builds
Lecture 5: Container Networking & Volumes
Lecture 6: Container Security & Module Wrap-up
Lecture 7: Configuration Management & Ansible Basics
Lecture 8: Playbooks, Roles & Variables
make real commits that tell a story — the add/commit/push loop from today's lecture, for real, on your own repo.
NOTES.md (in this same folder) has two placeholder lines. Do the following, as three separate commits (not one big commit at the end): 1. Edit NOTES.md — replace the "What I learned about Git today" placeholder with a real sentence. Commit it with a message describing what you learned, not just "update notes". 2. Edit NOTES.md again — replace the "A command I want to remember" placeholder with an actual Git command from today plus a one-line note on when you'd use it. Commit it separately from step 1. 3. Create a n
write a Containerfile from scratch for a small app — base image, dependency install, non-root user, in the right order — the skill from today's lecture.
Your repo has one folder: app/ — a tiny Flask app (app.py) and its requirements.txt. You did not write this app and don't need to touch it. Your job is to containerize it. Add a file named exactly Containerfile at the root of the repo (not inside app/) that:
write a multi-stage Containerfile that separates build-time tooling from the runtime image — the skill from today's lecture — and ship a final image under 50 MB.
Your repo has one folder: app/ — a tiny Go HTTP server (main.go) and its go.mod, responding with exactly INF345 Practice 04 OK on port 8080. You did not write this app and don't need to touch it. Your job is to containerize it properly — with a multi-stage build, not a single-stage one. Add a file named exactly Containerfile at the root of the repo (not inside app/) that:
wire two containers together with Compose — a user-defined network so they can reach each other by service name, and a named volume so data survives a restart — the skill from today's lecture.
Your repo has two things already done, which you do not need to touch: - app/ — a tiny Go HTTP server that serves a visit counter it keeps in Redis (GET /, reading REDISADDR, default redis:6379) and a liveness probe (GET /healthz). - app/Containerfile — already builds and runs correctly (last week's skill).
take a Containerfile that works and make it safe to ship: non-root, pinned, minimal, no secrets inside, and still working when the runtime takes almost every privilege away — the skills from today's lecture.
Your repo has: - main.go — a tiny Go HTTP server. Don't touch it. GET / answers INF345 Practice 06 OK uid=<uid> token=<set|missing> — it reports which user it runs as, and whether APITOKEN was given to it (never the value). On startup it writes a small file to /tmp. - .env — a local-development secret (APITOKEN=...). It's a fake token, but treat it exactly like a real one. - Containerfile — builds and runs fine. It is also a security review's worst nightmare. Every line marked PROBLEM is something to fix.
describe a small "deployment" as Ansible code — an inventory, one play, four real modules, variables — and prove it's idempotent: the second run changes nothing. The skills from today's lecture.
Your repo has these already done, which you do not need to touch: - ansible.cfg — points Ansible at inventory.ini. - files/index.html — a page to deploy. - templates/app.conf.j2 — a Jinja2 template for the app's config, using appname, appport and appenv.
take last week's playbook and turn it into a reusable role — variables as role defaults, tasks in the role, a handler that runs only when something changed — and see variable precedence at work: groupvars beat role defaults, -e beats everything. The skills from today's lecture.
Do the TODOs in order: 1. site.yml — one play on web that applies the role: roles: [webapp]. No tasks in site.yml itself. 2. roles/webapp/defaults/main.yml — approot: /tmp/inf345-p08/app, appname: inf345, appport: 8080, appenv: production. Defaults are the weakest variables — that's what lets users of the role override them. 3. – 6. roles/webapp/tasks/main.yml — last week's four tasks, now under {{ approot }}: the directory (file, 0755), index.html (copy, 0644), app.conf (template) and the APPENV line in .env (line
Turn To do, Coming up, and Earlier into a simple operating rhythm for technical work.
A safe sandbox for a failing push and a missing invite.
Inspect a starter topology and submit the checked artifact.
Answer five source-backed checks and keep your first score separate.
Each lecture and practice links back to its Maru item and the original INF 345 repository. The workspace keeps the imported brief visible while leaving the canonical source one click away.