Containerize it with Docker
Continues from the last build: TaskFlow runs natively on your laptop; "works on my machine" keeps biting teammates.
In the last rung you got TaskFlow running natively: a Vite React frontend, a FastAPI backend on :8000, and a local Postgres.
What you'll build
A teammate can clone the repo, run `docker compose up`, and have the full TaskFlow stack (nginx-served frontend, FastAPI backend, Postgres with persistent data) running and talking to each other in under two minutes, with zero local Python/Node/Postgres installs. The browser only ever talks to nginx on the host port; nginx serves the SPA and reverse-proxies /api to the backend, so there is no broken localhost:8000 call from the browser. You will understand multi-stage builds, why images run as non-root, how layer caching makes rebuilds fast, what .dockerignore keeps out, how an nginx reverse proxy joins frontend and backend on one origin, and how compose service names act as DNS hostnames between containers.
See how we teach, before you sign up
You don't just get code dumped on you. Every starter file and every solution is explained line-by-line, in plain English. Here's one real file from this project:
# Pin a specific minor so rebuilds are reproducible across laptops.
FROM python:3.12-slim AS runtime
# Don't write .pyc files; flush logs straight to the container's stdout.
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1
WORKDIR /app
# Copy ONLY requirements first so this layer is cached until deps change.
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Now copy the app code (changes often, so it goes after the deps layer).
COPY app ./app
# Create an unprivileged user and hand it the workdir.
RUN useradd --create-home --uid 10001 appuser \
&& chown -R appuser:appuser /app
USER appuser
EXPOSE 8000
# 0.0.0.0 so the port is reachable from outside the container, not just localhost.
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]Reading this file
FROM python:3.12-slim AS runtimeStarts from the slim Python image, which drops build tools and docs to keep the image small.COPY requirements.txt .Copies just the dependency list before the app code so Docker can cache the slow pip install step.RUN pip install --no-cache-dir -r requirements.txtInstalls Python deps; --no-cache-dir avoids leaving pip's download cache inside the image.USER appuserSwitches to a non-root user so a compromised process inside the container has fewer privileges.CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]The default command that launches the FastAPI app with uvicorn on all interfaces, port 8000.
Slim Python image, deps cached as their own layer, runs as a non-root user.
That's 1 of 10 explained code blocks in this single project.
The build, milestone by milestone
- 1
Write the backend Dockerfile (slim, non-root, layer-cached)
5 guided stepsThe backend is the heart of TaskFlow. Getting its image right (small, reproducible, non-root) is the pattern you will reuse for every service you containerize for the rest of your career.
- 2
Add a .dockerignore and confirm the build context shrinks
4 guided stepsA fat build context slows every build and can leak secrets (a local .env) or bloat the image (a 300MB node_modules that gets reinstalled anyway). .dockerignore is the cheapest image-hygiene win there is.
- 3
Multi-stage frontend Dockerfile + nginx reverse proxy
5 guided stepsShipping the Node build toolchain to production is wasteful and widens the attack surface, so multi-stage builds ship only the static artifact. The nginx reverse proxy is the other half: it lets the browser-side SPA reach the backend over the same origin it loaded from, instead of trying to call a compose service name (which only resolves container-to-container) or a host port that may not be published. This single-origin pattern is exactly how you will front services behind an ALB and an Ingress later in this ladder.
- 4
Wire it all together with docker-compose.yml
5 guided stepsThis is the payoff: one file, one command, the whole stack. Service-name DNS is how containers find each other, both for the backend reaching Postgres and for nginx reaching the backend. Understanding it is foundational for every orchestrator you will touch, including Kubernetes later in this ladder.
- 5
Add healthchecks and depends_on ordering
5 guided stepsdepends_on alone only waits for a container to START, not to be READY. Postgres takes a few seconds to accept connections; without a healthcheck gate the backend crashes on boot in a race, and nginx returns 502 if it proxies to a not-yet-ready backend. This is the exact readiness pattern you will reuse for ALB target groups and Kubernetes probes later.
- 6
Optimize, document, and prove the one-command onboarding
5 guided stepsThe whole point of this rung is killing 'works on my machine'. A capstone is only done when someone else can reproduce it cold. This is also exactly what a hiring manager looks for: can you make your work runnable by others?
What's inside when you start
You'll walk away with
This is portfolio-grade. Build it free.
Sign up to unlock every milestone step-by-step, the code skeletons, full reference solutions, and checkable tasks, with your progress saved as you build.
Start building