← back to blog

How I dockerized my first production app (and what I got wrong)

I thought Dockerizing a FastAPI app would take an afternoon. It took three days, and I learned more about Linux, networking, and environment management than I expected.

The naive Dockerfile I started with

My first attempt looked like this:

FROM python:3.11
COPY . /app
WORKDIR /app
RUN pip install -r requirements.txt
CMD ["uvicorn", "main:app", "--host", "0.0.0.0"]

This works. But it's terrible. Every code change rebuilds the entire pip install layer because I copied source before installing dependencies.

Mistake 1: Layer order matters

Docker caches layers. If you copy requirements.txt first, install dependencies, then copy your code — pip only reruns when the requirements actually change.

FROM python:3.11-slim
WORKDIR /app

# Dependencies layer (cached until requirements change)
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# Code layer (rebuilds on every change — fast)
COPY . .

CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]

Mistake 2: Running as root

By default containers run as root. That's a security problem. Adding USER nobody before the CMD line is a two-second fix with real impact.

What I'd do differently

The full source for this project is on GitHub. Next post: setting up docker-compose for local development with hot reload.