FastAPI: From Basics to Production · Deployment, Observability and Performance

Docker Images for FastAPI

Build small, cache-friendly, non-root images.

Use an official slim Python base, install dependencies before copying source (layer caching), consider uv for fast, locked installs, run as a non-root user, and use the exec form of CMD so signals reach the server. Keep secrets out of the image (inject at runtime) and add a health endpoint for orchestrators.

FROM python:3.13-slim
ENV PYTHONDONTWRITEBYTECODE=1 PYTHONUNBUFFERED=1
COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv
WORKDIR /app
COPY pyproject.toml uv.lock ./
RUN uv sync --frozen --no-dev --no-install-project
COPY . .
RUN useradd --create-home appuser && chown -R appuser /app
USER appuser
EXPOSE 8000
CMD ["/app/.venv/bin/fastapi", "run", "app/main.py", "--port", "8000"]

Common pitfalls

  • Copying the whole repo before installing dependencies (cache busts on every change).
  • Shell-form CMD swallowing SIGTERM.
  • Baking .env files into images.

Key points

  • Dependencies before source for caching
  • Non-root, exec-form CMD
  • Secrets injected at runtime

Further reading: FastAPI: Docker

Practice questions

Why use exec-form CMD ["..."] instead of shell form?

Answer

Exec form runs the server as PID 1 directly so it receives SIGTERM and can shut down gracefully; shell form wraps it in /bin/sh -c, which may not forward signals.

What does PYTHONUNBUFFERED=1 do in containers?

Answer

It disables output buffering so logs appear immediately in docker logs / the log collector.

Open in the app to mark lessons complete, take notes and track your progress.