Unit 14.02: A container that starts the same way every time
The container should start the same way every time, and layer ordering is what makes rebuilds fast.
Dependencies first, source last
A Dockerfile with each line's reason.
The code lists them.
DOCKERFILE = [
("FROM python:3.12-slim", "pinned minor version"),
("WORKDIR /app", ""),
("COPY pyproject.toml ./", "dependencies first, so the layer caches"),
("RUN pip install --no-cache-dir .", "no pip cache in the image"),
("COPY app ./app", "source last; changes here skip the install"),
("USER 1000", "do not run as root"),
("EXPOSE 8000", ""),
('CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--workers", "4"]',
"no --reload"),
]
for line, note in DOCKERFILE:
print(f"{line:58} {note}")
print("""
The ordering is what makes rebuilds fast: dependencies change rarely and get
cached, source changes constantly and sits in the last layer.
`USER 1000` and the absence of `--reload` are the two lines most often missing
from a container that started life as a development one.
""")
Dependencies change rarely and sit in an early layer that caches; source changes constantly and sits last, so a code change skips the install entirely. Reversing that order means reinstalling everything on every commit.
USER 1000 and the absence of --reload are the two lines most often missing from a container that began life as a development one.
The mistake this prevents
The mistake is copying the whole directory before installing. Every source change then invalidates the dependency layer, and a one-line fix takes a full rebuild.
Takeaway
Copy dependency manifests and install before copying source, run as a non-root user, and never ship --reload.
