Docker cookbook · Beginner
Build a production image for a Python web app
Package a Python API (FastAPI, Flask, Django) in a small, cache-friendly, non-root image.
Approach
Use a slim base, install dependencies from a lockfile in their own layer, then copy the code. Create a non-root user, set Python's unbuffered output, and start the server in exec form.
Solution
# syntax=docker/dockerfile:1
FROM python:3.14-slim
ENV PYTHONDONTWRITEBYTECODE=1 PYTHONUNBUFFERED=1
WORKDIR /app
COPY requirements.txt .
RUN --mount=type=cache,target=/root/.cache/pip pip install -r requirements.txt
RUN useradd --uid 10001 --no-create-home app
COPY src/ ./src/
USER 10001
EXPOSE 8000
CMD ["gunicorn", "--bind", "0.0.0.0:8000", "--workers", "2", "-k", "uvicorn.workers.UvicornWorker", "src.main:app"]
Pitfalls
- Copying the source before installing dependencies, so every code edit reinstalls packages.
- Binding the server to 127.0.0.1 inside the container.
- Running as root because the base image does.
Variations
- Use
uv(COPY --from=ghcr.io/astral-sh/uv:latest /uv /bin/uv, thenuv sync --frozen) for faster installs. - Build wheels in a builder stage when dependencies need compilers, then copy only the wheels.
Further reading: Dockerfile best practices
Reference: https://docs.docker.com/build/building/best-practices/
More Docker recipes
- Add a healthcheck to an HTTP service
- Build a layered image for a Spring Boot app
- Build a production image for a Node.js service
- Build, cache and push images in GitHub Actions
- Build one image tag for amd64 and arm64
- Configure nginx from environment variables
- Develop with Compose and live reload
- Make containers cope with dependencies that start slowly