Dockerfile Builder for Python

Generate a lean Dockerfile for Flask, FastAPI, or Django apps

Build a multi-stage Dockerfile for Python web apps. Picks Python version, framework (FastAPI, Flask, Django), port, a /health HEALTHCHECK and ENTRYPOINT, installs from requirements.txt and runs as a non-root user. It runs free in your browser on Gera Tools, with nothing uploaded.

Last updated Source: Gera Tools

Why build wheels in a separate stage?

The builder stage compiles every dependency into wheels, then the runtime stage installs them with --no-index. This keeps the C compilers and build headers out of the final image while still supporting packages that need compilation.

A builder for a lean, multi-stage Dockerfile for Python web apps — FastAPI, Flask or Django. Choose your Python version, framework and port, and the tool emits an image that builds dependency wheels in one stage and ships a slim runtime that runs as a non-root user.

How it works

The generated Dockerfile uses two stages. The builder stage runs pip wheel to compile every dependency from requirements.txt into a /wheels directory, keeping compilers and headers out of the final image. The runtime stage installs those pre-built wheels with pip install --no-index --find-links=/wheels, sets PYTHONUNBUFFERED=1 and PYTHONDONTWRITEBYTECODE=1, creates a dedicated non-root app user, copies your code, exposes the port and defines an ENTRYPOINT.

Framework start commands

FastAPI is served with uvicorn app.main:app --host 0.0.0.0, Flask with gunicorn --bind 0.0.0.0:PORT app:app, and Django with gunicorn --bind 0.0.0.0:PORT myproject.wsgi:application. Override the start command if your module path differs. The optional HEALTHCHECK calls your /health endpoint using the standard-library urllib, so it adds no extra dependency.

Tips

Keep dependencies in a pinned requirements.txt so builds are reproducible. Add a .dockerignore so caches and secrets are not baked into the image:

__pycache__
*.pyc
.venv
.git
.env

The Debian-based slim base image is chosen deliberately: it works with the prebuilt manylinux wheels most packages ship, avoiding the slow source builds and musl-libc issues common with alpine.

What the generated Dockerfile looks like

A FastAPI app on Python 3.12 might produce:

# Builder stage
FROM python:3.12-slim AS builder
WORKDIR /wheels
COPY requirements.txt .
RUN pip wheel --no-cache-dir --wheel-dir /wheels -r requirements.txt

# Runtime stage
FROM python:3.12-slim
ENV PYTHONUNBUFFERED=1 PYTHONDONTWRITEBYTECODE=1
RUN addgroup --system app && adduser --system --ingroup app app
WORKDIR /app
COPY --from=builder /wheels /wheels
RUN pip install --no-cache-dir --no-index --find-links=/wheels /wheels/*.whl
COPY --chown=app:app . .
USER app
EXPOSE 8000
HEALTHCHECK --interval=30s --timeout=5s \
  CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')"
ENTRYPOINT ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]

Why two stages for Python

The wheel-build stage may require C headers, compilers, and Rust (for packages like cryptography or pydantic-core). These can add hundreds of megabytes to a dev-sized base image. The runtime stage only needs the precompiled .whl files — the compilers never touch it. The result is a final image that is typically 60–70% smaller than a single-stage build with the same dependencies.

Key environment variables explained

PYTHONUNBUFFERED=1 forces Python to flush stdout and stderr immediately without buffering, which is essential in containers so that log lines appear in real time rather than being batched. PYTHONDONTWRITEBYTECODE=1 prevents Python from writing .pyc files to disk, keeping the container filesystem clean and avoiding permission issues when the source is mounted read-only.

Scaling uvicorn and gunicorn workers

For production workloads, pass the worker count explicitly:

  • uvicorn: --workers 4 runs four processes. As a rule of thumb, 2 × CPU_count + 1 is a common starting point for I/O-heavy apps.
  • gunicorn: --workers 4 --worker-class uvicorn.workers.UvicornWorker gives you async uvicorn workers managed by gunicorn’s process supervisor, which handles graceful restarts and worker recycling.

For containerised deployments, set workers to a small fixed number (2–4) and scale via container replicas rather than packing many workers into one container — this keeps memory usage predictable and crash recovery fast.