Throughout this chapter, you've built Docker knowledge step by step: container fundamentals, Dockerfile syntax, lifecycle management, multi-stage builds. Now it's time to apply everything to a real production scenario.
In Chapter 70, you built a Task API with SQLModel and Neon PostgreSQL. It works on your machine. But "works on my machine" doesn't ship products. Your teammates can't run it without matching your Python version, installing the same dependencies, and configuring their environment variables.
This capstone changes that. You'll write a specification FIRST, then containerize your API using the patterns from this chapter. The result: a portable container image that runs identically on your laptop, a teammate's machine, or a cloud server.
The specification-first approach is critical. Jumping straight to code is the Vibe Coding anti-pattern. Writing the spec first forces you to think about constraints (image size, security, configuration) before touching any Docker commands.
Before any implementation, you write a specification. This is the specification-first approach that separates professional development from Vibe Coding. The spec defines WHAT you're building and HOW you'll know it works.
Create containerization-spec.md in your project directory:
Why specification first?
Without a spec, you'd start typing FROM python:3.12 and figure things out as you go. That's Vibe Coding. You might forget security constraints. You might not consider image size until it's 1.2GB. You might hardcode secrets.
The spec makes constraints explicit BEFORE you start. It's your contract with yourself.
Before writing the Dockerfile, ensure your Task API code is ready for containerization.
Your project should have this structure:
Verify requirements.txt includes all dependencies:
Update config.py to read DATABASE_URL from environment:
Add a health check endpoint to main.py:
Output:
Now apply the multi-stage build pattern from Chapter 6. Reference your specification: under 200MB, Alpine base, non-root user.
Create Dockerfile:
Key design decisions (trace back to spec):
Build the image and validate against success criteria from your spec.
Build the image:
Output:
Check image size (spec: under 200MB):
Output:
145MB is well under the 200MB target from your specification.
Run the container with DATABASE_URL:
docker ps
Test health check (spec: health endpoint responds):
Output:
Test CRUD endpoints (spec: all endpoints work):
Verify non-root user (spec: container runs as non-root):
Output:
Your image works locally. Now push it to a registry so anyone can pull and run it.
Step 1: Log in to Docker Hub
Enter your Docker Hub username and password when prompted.
Step 2: Tag the image for your Docker Hub account
Step 1: Create a personal access token
Go to GitHub Settings > Developer settings > Personal access tokens > Generate new token. Select write:packages scope.
Step 2: Log in to GHCR
Step 3: Tag and push
docker pull yourusername/task-api:v1
docker logs task-api-container
docker exec task-api-container env | grep DATABASE
DATABASE_URL=postgresql://user:pass@host/db?sslmode=require
docker exec task-api-container wget --spider http://localhost:8000/health
docker logs task-api-container | head -20
docker exec task-api-container ls -la /app