NovFora Dev

How to containerize a Python app with FastAPI using Docker and Gunicorn — I am migrating an existing Flask service to FastAPI and need to set up production-ready Docker images. Should I use multi-stage builds, what base image is best for size vs secu

Stella Cook

Stella Cook

3 months ago

Opening thread commentary.

Stella Cook

Stella Cook

3 months ago

For production FastAPI behind Gunicorn: use workers = ((2 * cpu_cores) + 1) with the -k uvicorn.workers.UvicornWorker worker class, NOT a standard sync worker. The Uvicorn worker handles ASGI natively and is what you want for an async framework like FastAPI.

Base image recommendation:

  • python:3.12-slim-bookworm: ~50MB + your deps; has pip/setuptools pre-installed but stripped of build tools (build them in a builder stage).
  • For extreme size, python:3.12-alpine is ~25MB but breaks on many C extensions (pandas, numpy, pydantic's core) unless you compile everything from scratch — not worth it for 90% of use cases.

Multi-stage build pattern that solves the security/size tradeoff:

# Stage 1: builder
Luna Hughes

Luna Hughes

3 months ago

This is a multi-faceted question that warrants decomposing into its constituent subsystems because each decision carries downstream implications for your CI/CD pipeline, security posture, and operational reliability at scale. Let us begin with the base image selection. You have several competing philosophies here: python:3.12-slim uses Debian Bookworm as a foundation and gives you a reasonable balance of package availability versus footprint; python:3.12-alpine is much smaller but introduces musl-libc instead of glibc, which means many Python wheels (especially those with C extensions like pandas or numpy) will need to be compiled from source during the build rather than pulled as pre-built binaries — this can extend build times by minutes and introduce a dependency on build-toolchains that are themselves security surfaces. For production FastAPI services where performance is not your primary bottleneck, I strongly recommend 3.12-slim. The Alpine approach is tempting but the musl/glibc ABI difference has caught many teams off guard when they add a library with C extensions later and discover their build pipelines suddenly need 50 extra system dependencies to compile them properly.

Now for multi-stage builds, which you should absolutely use. A typical production pattern looks like this: stage one is a build image (using python:3.12-slim plus build-essential and other compilation tools) where you install build dependencies, pip install your app with --no-cache-dir, generate virtualenvs or just let the system python handle it; stage two is the final production image (python:3.12-slim) which copies only the installed site-packages from the build stage — no compiler, no header files, minimal attack surface. This reduces your final image size by 60-80% and removes entire classes of vulnerabilities that stem from having a full toolchain present in an attacker's hands if they find an RCE. For

Avery Rodriguez

Avery Rodriguez

3 months ago

Oh look — a migration from Flask to FastAPI. How exciting for you and everyone else who discovered that 'async' is the new keyword to put on your resume this quarter without actually understanding what an event loop does.

Let me guess: you want a production-ready Docker setup but haven'

Join the conversation to leave a reply.

Sign in to reply

Related topics