Goal: Build the seed of your DevOps Info Service β a small HTTP service that reports its own metadata + the host it runs on. Every later lab grows this same service. Deliverable: A PR from
lab01withapp_python/(and optionallyapp_go/). The image you build over the next 16 weeks starts here.
In this lab you will practice:
- Picking a Python web framework and justifying the choice
- Reading host/runtime info from
platform,socket,os - Wiring HTTP routes that return JSON (not HTML)
- Pinning dependencies, configuring via environment variables, writing real docs
β οΈ Scope: no database, no auth, no Docker yet. That comes in Lab 2+. Don't get clever β keep the code short and obvious.
You should have from previous labs:
- Nothing β this is week 1.
This lab adds:
app_python/β Python service withGET /(info) andGET /health(probe)- (optional bonus)
app_go/β compiled-language sibling with the same endpoints
By Lab 9 this service runs on Kubernetes; by Lab 13 ArgoCD deploys it. So the choices you make this week β framework, layout, env-var config β outlive Lab 1.
You need only:
python3 --version # 3.12+ (course standardizes on 3.13)
pip3 --versionπ
pythonvspython3: on Debian/Ubuntu the binary ispython3; on macOS/Windows it may bepython. Pick whichever exists on your box β the rest of this lab writespython app.pyfor brevity, substitutepython3 app.pyif that's how your system spells it.
Create the directory layout (you'll fill the files yourself):
app_python/
βββ app.py
βββ requirements.txt
βββ .gitignore
βββ README.md
βββ tests/
β βββ __init__.py # empty; tests come in Lab 3
βββ docs/
βββ LAB01.md # your submission report
You may use Flask 3.1, FastAPI 0.136, or Django 5.2 (overkill for this; do it if you want the practice). In docs/LAB01.md, give your one-paragraph reasoning + a small comparison table covering at least: footprint, async support, OpenAPI/docs, learning curve.
YOUR TASK: write a handler at the root path that returns JSON with five top-level keys: service, system, runtime, request, endpoints.
Requirements:
service:name,version,description,framework(your choice from 1.1)system:hostname,platform,platform_version,architecture,cpu_count,python_versionruntime:uptime_seconds(int),uptime_human(e.g."1 hour, 3 minutes"),current_time(ISO 8601 UTC),timezonerequest:client_ip,user_agent,method,pathendpoints: list of{path, method, description}for every route you publish
Hints:
platform.system(),platform.machine(),platform.python_version()cover most ofsystemsocket.gethostname()for the hostname- Uptime =
datetime.now(timezone.utc) - START_TIMEcaptured at module import β be timezone-aware throughout (mixing naive + aware datetimes raisesTypeError) - Per-framework request access: Flask β
request.remote_addr/request.headers.get('User-Agent'); FastAPI βrequest.client.host/request.headers.get('user-agent')
π‘ Stuck on the structure?
Sketch β no Python code given, intentionally:
imports β app object β constants (SERVICE_NAME, START_TIME, ...) β helper get_system_info() β helper get_uptime() β route handler returning the 5-key dict β error handlers β __main__ block
Lecture 1 slide on the lifecycle, lecture 2 on configuration β both apply here.
YOUR TASK: write a second handler that returns {status, timestamp, uptime_seconds} and status code 200. This will be your Kubernetes liveness/readiness target in Lab 9 β keep it cheap (no DB calls, no external HTTP).
YOUR TASK: read HOST, PORT, DEBUG from the environment with sensible defaults (0.0.0.0, 5000, False). The app must run unchanged with:
python app.py # default 0.0.0.0:5000
PORT=8080 python app.py # custom port
HOST=127.0.0.1 PORT=3000 python app.py # bothHint: cast PORT to int and DEBUG to bool by lowercase string comparison (os.getenv("DEBUG","False").lower() == "true") β bool("False") is True in Python; that's the easy way to ship a debug-mode bug to production.
YOUR TASK: register handlers for 404 and 500 that return JSON (matching the rest of the API), and configure stdlib logging at INFO level with a format including timestamp/level. Don't print().
Paste into docs/LAB01.md:
- Four CLI captures, with the exact commands you ran:
curl -s http://localhost:5000/ | jq .β full JSON, your real hostname + uptimecurl -s http://localhost:5000/health | jq .curl -s -o /dev/null -w "HTTP %{http_code}\n" http://localhost:5000/nopeβ proves the 404 handlerPORT=8080 python app.pystarted in another shell + acurlshowing the new port served β proves env-var config
- The framework comparison table from 1.1
- 2β3 sentences on the biggest gotcha you hit and how you solved it
π‘ Hints if you're stuck
- Make the framework choice early β switching mid-lab burns time.
- Build
GET /last. BuildGET /healthfirst (5 lines), then incrementally add system info, uptime, request info. - If JSON output looks "weird" (e.g., bytes prefix
b'...'), you're returning a string instead of a dict β let the framework jsonify.
YOUR TASK: write the user-facing README with these sections (no fluff, prefer commands over prose):
- Overview β one paragraph
- Prerequisites β Python version, OS
- Installation β
python -m venv venvβ¦pip install -r requirements.txt - Running β default + custom-port examples
- API Endpoints β table of
Method | Path | Description - Configuration β table of env vars + defaults + purpose
YOUR TASK: ship the things every Python service needs.
| File | Must contain |
|---|---|
requirements.txt |
The framework pinned to an exact patch (e.g. Flask==3.1.3, not Flask>=3). One framework only β drop the rest. |
.gitignore |
__pycache__/, *.pyc, venv/, *.log, IDE dirs (.vscode/, .idea/), .DS_Store |
Imports in app.py |
Grouped (stdlib β third-party β local), PEP 8 compliant |
Required sections, in order:
- Framework Selection β your choice + comparison table
- Best Practices Applied β bulleted list, each with one sentence of why
- API Documentation β request + response example per endpoint
- Testing Evidence β the four CLI captures from 1.6
- Challenges & Solutions β at least one real one (not "I was new to Python")
- GitHub Community β one paragraph; see 2.4
This is the social half of being a developer. The actions are public on your profile.
YOUR TASK:
- Star the course repository.
- Star the simple-container-com/api project β a promising open-source tool for container management.
- Follow your professor and TAs on GitHub:
- Professor: @Cre-eD
- TA: @Naghme98
- TA: @pierrepicaud
- Follow β₯ 3 classmates from this course.
- In your
docs/LAB01.md"GitHub Community" section, put your GitHub username on the first line (so the TA can verify your stars/follows in <30s) then write 2β3 sentences answering: Why does starring a project actually help its maintainers? What concrete benefit do you get from following other developers? β your own words, not the lecture's.
Paste into docs/LAB01.md:
- The
docs/LAB01.mditself satisfies all six required sections β that is the proof. - Output of
find app_python -maxdepth 2 -type f | sortshowing every required file is present.
Re-implement the same service in a compiled language, in a sibling directory (e.g. app_go/).
Why bother? Lab 2's multi-stage Docker bonus shrinks a Go service from ~900 MB to ~15 MB β that doesn't work without a static binary to start with.
YOUR TASK:
- Same two endpoints (
/,/health) - Same JSON shape as your Python version (so the same
curlcalls work against both) - A README in the sibling dir explaining build + run
- A
go.mod(andgo.sumif you pull deps) β Lab 2's bonus needs them as the build context - In
docs/LAB01.md, add a one-line artifact size for both. Measure the same way for each so the comparison is apples-to-apples:- Python:
du -sh app_python/venv(the venv carries everything beyond the interpreter) - Compiled:
ls -lh app_go/<binary>(the single static binary) - The size delta is the whole point. The real Docker-image comparison comes in Lab 2.
- Python:
Choose one of:
| Language | Idiomatic HTTP | Notes |
|---|---|---|
| Go (recommended) | net/http |
Single static binary; instant compile; smallest distroless image later |
| Rust | actix-web / axum |
Strong types; longer compile; great for the security-minded |
| Java + Spring Boot | spring-boot-starter-web |
Industry-standard for enterprise β heavier runtime |
| C# + ASP.NET Core | WebApplication.Create |
Cross-platform .NET; comparable to Java but newer ergonomics |
Hints (no full code):
- For Go,
runtime.NumCPU(),runtime.GOOS,runtime.GOARCH,os.Hostname()mirror Python'sos/platform. JSON keys differ in casing β use struct tags (json:"hostname") to keep the same wire shape. - For Java/C#, start from
spring init/dotnet new webβ don't hand-writepom.xml/csproj.
git switch -c lab01
git add app_python/
git add app_go/ # only if you did the bonus
git commit -m "feat(lab01): devops info service β python (+ go bonus)"
git push -u origin lab01Open two PRs:
your-fork:lab01βcourse-repo:master(reviewed)your-fork:lab01βyour-fork:master(merges into your own main when done)
PR checklist:
- [ ] Task 1 done β /, /health, env config, error handlers, logging
- [ ] Task 2 done β README, requirements.txt, .gitignore, docs/LAB01.md, GitHub social
- [ ] Bonus done β app_go/ (or other) with same endpoints + size comparison
- β
Service starts with
python app.pyand serves on0.0.0.0:5000 - β
GET /returns HTTP 200 with all five top-level keys populated from your real machine - β
GET /healthreturns HTTP 200 with the three required fields - β 404 handler returns JSON (not HTML)
- β
PORTenv var overrides the port - β Logging is configured (you'll see startup log line in stdout)
- β
README.mdhas all six sections - β
requirements.txtpins an exact version - β
.gitignorecovers Python + IDE + OS artifacts - β
docs/LAB01.mdhas all six sections including the GitHub Community paragraph - β Stars + follows visible on student's GitHub profile
- β Sibling app builds and serves the same two endpoints
- β
Wire-compatible JSON (same
curl β¦ | jqworks against both) - β
Artifact-size comparison documented (
du -sh venvvsls -lh <binary>) - β
Build manifest present (
go.mod/Cargo.toml/pom.xml/*.csproj) so Lab 2's bonus has a build context toCOPY
| Task | Points | Criteria |
|---|---|---|
| Task 1 β Python web app | 6 | Both routes correct, env-var config, error handlers, logging |
| Task 2 β Docs & hygiene | 4 | All file/section requirements met; pinned deps; complete LAB01.md |
| Bonus β Compiled sibling | 2 | Same endpoints, wire-compatible JSON, size comparison |
| Total | 12 | 10 main + 2 bonus |
π Documentation
- Flask 3.1 β the canonical micro-framework
- FastAPI β async, OpenAPI free
- Django 5.2 β full-stack; overkill for this lab
- Python
platform/socket - PEP 8, PEP 660
β οΈ Common Pitfalls (from real dry-runs)
- Naive vs aware datetimes β
datetime.now() - START_TIMEraisesTypeErrorif one is timezone-aware and the other isn't. Pick one (usedatetime.now(timezone.utc)everywhere) and stay there. bool("False") == Trueβ never writeDEBUG = bool(os.getenv("DEBUG", "False")). Use a lowercase string compare.- Flask 3 deprecation warning on
flask.__version__β useimportlib.metadata.version("flask")if you need to print the framework version. - Returning a dict directly from FastAPI vs Flask β FastAPI auto-serializes; Flask needs
jsonify(...). Don't return a bare dict from a Flask handler. socket.gethostname()inside a container later returns the container ID, not your laptop name β that's correct, not a bug. You'll see it again in Lab 2.- Port 5000 occupied on macOS β macOS Monterey+ runs the AirPlay Receiver on
:5000. The Flask default conflicts; you'll see yourcurlhit Apple's service instead. Either turn AirPlay Receiver off (System Settings β General β AirDrop & Handoff) or pickPORT=5050for your default run. pip install -r requirements.txtoutside the venv β installs Flask system-wide, thenpython app.pymay still pick up a stale Flask elsewhere. Always activate the venv (source venv/bin/activate) beforepip installand beforepython app.py.- Don't return a Response from
@app.errorhandler's default β Flask's 500 handler must return a tuple(body, status). Returning a barejsonify(...)sends HTTP 200 with the error JSON β a subtle bug that breaks the Lab 9 probe later.
π οΈ Dev tools worth knowing
| Lab | What it adds to this service |
|---|---|
| 2 | Multi-stage Dockerfile, image scan, push to a registry |
| 3 | CI: pytest + lint + image build + Trivy gate on every PR |
| 7 / 8 | Structured JSON logs (Loki + Alloy) and a /metrics endpoint (Prometheus) |
| 9 / 10 | Deploy to Kubernetes (k3d) + Helm 4 chart |
| 11 / 12 | OpenBao for secrets, ConfigMaps + PVCs for state |
| 13 / 14 | ArgoCD GitOps + canary rollouts |
Keep the code simple. You'll come back and rewrite parts of it; that's the point.