Zero to Deployed on a €0 Bill: Teaching Cloud to TU Delft Students
July 2026 · 8 min read
The brief I set myself was simple to state and annoying to satisfy. Take university students who can write Python, and get every one of them to a personal API running in the cloud, deploying automatically on every commit. Without a single credit card entering the story.
That last constraint is the whole design. Students don't have corporate accounts. A course that begins with "first, set up billing" has already lost the room, and one that might surprise a student with a €40 bill is irresponsible. So the course I built for TU Delft students treats free tiers not as a nice-to-have but as a hard requirement that shapes every technical choice.
Nine exercises, one continuous artifact
The course is nine progressive exercises, and the crucial property is that they build one thing. Not nine toy examples. A single FastAPI service that each exercise makes more real.
The arc deserves spelling out, because the ordering is doing pedagogical work. Exercise one is the API itself. Three endpoints (root, health, version), Pydantic response models, type hints everywhere, and configuration through pydantic-settings reading environment variables. That last one is planted early on purpose, so that by the time containers arrive, "config comes from the environment" is already muscle memory rather than a new idea arriving at the same time as Dockerfiles. Exercise two is testing with pytest, async support and coverage included. Three introduces uv for dependency management. Four containerizes the app, and students complete a partial Dockerfile rather than copy a finished one. Five wires GitHub Actions to run quality checks on every push. Then the cloud half: six pushes the image to Artifact Registry, seven deploys it to Cloud Run by hand, eight automates the whole path so a push deploys itself, and nine hardens what now exists. Security, observability, the topics that only make sense once there's something real to protect.
By the end, a student's repository is a small but genuine production setup, and the final state has a property I care about:
flowchart LR
PUSH["git push"] --> QC["GitHub Actions<br/>ruff lint · ruff format<br/>mypy · pytest"]
QC -->|all green| BUILD["docker build"]
BUILD --> AR["Artifact Registry"]
AR --> CR["Cloud Run<br/>(scale to zero)"]
CR --> HC["deploy health check<br/>GET /health"]
QC -.->|red| STOP["pipeline stops:<br/>broken code never ships"]
WIF["Workload Identity Federation<br/>no service-account keys, anywhere"] -.authenticates.-> AR
Every commit they push triggers quality checks, builds a container, ships it, and then curls its own /health endpoint to confirm the thing that deployed actually runs. That last small step separates deployment from hope. The moment a student pushes a change and watches their live URL update on its own is the moment the course actually lands.
You can lecture about CI/CD for an hour, or you can let someone feel it once.
Teach the tools professionals actually use
An early decision: no teaching-only technology. Everything in the stack is what they'd meet at a decent engineering shop in 2026. FastAPI, pytest, uv (watching pip users meet uv's install speed is reliable entertainment), ruff and mypy in CI, Docker, GitHub Actions, Cloud Run. The argument for this is respect. Students can smell a sandbox.
The lint configuration is itself a tiny syllabus. Ruff runs with rule families beyond the default errors. Bugbear for the mistakes that pass tests and fail at 2am, comprehension and upgrade rules, simplification hints. Mypy runs strict. Students meet "the linter is arguing with me" early, in exercise five, on code they wrote themselves. And the resolution process (read the rule, understand why, fix or consciously silence it) is rehearsed as a professional skill rather than an annoyance. The same philosophy runs through the troubleshooting doc, which is a first-class part of the course. Real tools come with real sharp edges, and "read the error, check the docs, fix the config" is the actual curriculum hiding inside every exercise.
And exercise two ships with one test that fails on purpose. Students' first encounter with pytest is red, not green. Because the skill isn't running tests, it's reading a failure and reasoning backward from the assertion to the cause. A wall of green teaches nothing except which button runs the suite. The failing test gets fixed in the first ten minutes and pays for itself all course long. It's a trick I've now smuggled into every course I write.
Free tiers as an architecture constraint
The budget engineering turned out to be some of the most interesting design work. The pillars:
GitHub Codespaces as the standard dev environment. 120 free core-hours per month, which on a 2-core machine is 60 hours, several times the course. It also deletes the entire "works on my machine" support queue. Everyone gets an identical Linux box with the toolchain pre-installed, and laptop diversity stops being my problem. In a room of thirty students, environment triage is the difference between teaching and tech support.
Cloud Run as the deployment target, and not just for the generous free tier. 180,000 vCPU-seconds a month is far more than a student API sipping requests will ever use, sure. But scale-to-zero is also the pedagogically honest choice. An idle student service costs actual zero, and thirty forgotten course deployments in December cost the same. Any always-on option would slowly bill someone for a tutorial they finished months ago. One shared GCP project hosts all the cloud exercises, so the setup ceremony happens once, in exercise six, instead of once per service.
Workload Identity Federation for the CI-to-GCP handshake, which I insisted on despite it being the fiddliest setup step in the whole course. The alternative, exporting a service-account key and pasting it into GitHub secrets, is easier to teach and exactly the habit I refuse to install in someone's first cloud experience. Long-lived credentials in CI is how universities end up mining someone else's cryptocurrency. Students configure OIDC trust between GitHub and GCP once, keys never exist, and their very first pipeline is built the way security teams wish all pipelines were.
What I'd keep
The through-line artifact, without question. One growing project generates a momentum that isolated exercises never find, the same lesson as the narrative structure in my agent course. The free-tier constraint too, and not only for the students' sake. Designing under "this must cost nothing" forced choices (serverless, scale-to-zero, keyless auth, ephemeral dev environments) that are simply good architecture, budget or not. Constraints that sound like austerity keep turning out to be taste.
There's a decent chance some of these students will one day inherit a real production pipeline. My quiet hope is that when they meet a service-account key pasted into a CI secret, something in the back of their mind, installed in exercise eight on a free tier years earlier, says: wait, we don't do it like that.
References
- FastAPI: The course's web framework. Type hints drive validation and live API docs, which means students' first API teaches Python typing, HTTP and JSON schemas at once. Modern, widely used professionally, and the auto-generated /docs page gives instant visual feedback.
- uv: A Rust-based Python package and project manager, orders of magnitude faster than pip, with lockfiles by default. Teaching it means teaching reproducible environments, and its speed keeps the toolchain feeling instant on the free 2-core machines.
- Ruff: An extremely fast Python linter and formatter that consolidates dozens of older tools. In the course it's the first quality gate in CI, and its named, documented rules make 'the linter is arguing with me' a learnable skill instead of noise.
- mypy: Static type checking for Python, run in strict mode. It catches the category of bug tests rarely cover (wrong shapes passed between functions) before the code runs, and it makes the type hints from exercise one load-bearing rather than decorative.
- pydantic-settings: Typed configuration loaded from environment variables. It plants the twelve-factor 'config comes from the environment' habit in exercise one, so containerization later changes nothing about how the app is configured.
- Artifact Registry: Google Cloud's container image registry. It's the handoff point of the pipeline: CI pushes images to it, Cloud Run pulls from it, and access is granted by IAM rather than passwords.
- Cloud Run: Serverless containers with scale-to-zero. The deployment target because an idle student API costs exactly nothing, and because 'give me a container, get a URL' is the shortest path from Docker knowledge to a live service.
- Workload Identity Federation: Keyless authentication from GitHub Actions to GCP: the pipeline exchanges a short-lived OIDC token for cloud access, based on a configured trust between the repo and the project. It eliminates exported service-account keys, the most commonly leaked credential in student and professional pipelines alike.
- GitHub Codespaces: Browser-based dev environments from a repo config, identical for every student. It removes laptop diversity from the classroom equation, and the free tier covers the course several times over.