docker

Dockerfile for Python

Production-ready Dockerfile for Python applications with virtual environments, slim base image, and security hardening.

Overview

A well-structured Dockerfile for Python applications (Flask, FastAPI, Django, etc.) using a slim base image, virtual environment isolation, non-root user, and optimized layer caching for fast rebuilds.

Configuration

# Dockerfile

# ── Build Stage ──
FROM python:3.12-slim AS builder

# Prevent Python from writing .pyc files and buffering stdout/stderr
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1

WORKDIR /app

# Install system dependencies for building Python packages
RUN apt-get update && apt-get install -y --no-install-recommends \
    build-essential \
    libpq-dev \
    && rm -rf /var/lib/apt/lists/*

# Create virtual environment
RUN python -m venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"

# Install Python dependencies
COPY requirements.txt .
RUN pip install --no-cache-dir --upgrade pip && \
    pip install --no-cache-dir -r requirements.txt

# ── Production Stage ──
FROM python:3.12-slim AS production

# Runtime environment variables
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1
ENV PATH="/opt/venv/bin:$PATH"

WORKDIR /app

# Install only runtime system dependencies (no build tools)
RUN apt-get update && apt-get install -y --no-install-recommends \
    libpq5 \
    curl \
    && rm -rf /var/lib/apt/lists/*

# Create non-root user
RUN groupadd -r appgroup && useradd -r -g appgroup -d /app appuser

# Copy virtual environment from builder
COPY --from=builder /opt/venv /opt/venv

# Copy application source code
COPY . .

# Set ownership to non-root user
RUN chown -R appuser:appgroup /app

# Switch to non-root user
USER appuser

# Expose application port
EXPOSE 8000

# Health check
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
    CMD curl -f http://localhost:8000/health || exit 1

# Run with Gunicorn (for Flask/Django) or Uvicorn (for FastAPI)
CMD ["gunicorn", "app:app", "--bind", "0.0.0.0:8000", "--workers", "4", "--threads", "2", "--timeout", "120"]

Key Options Explained

  • python:3.12-slim — Slim variant is ~150MB vs ~900MB for the full image. Includes just enough for most Python apps.
  • Virtual environment (/opt/venv) — Isolates dependencies and makes them easy to copy between stages without carrying build tools.
  • PYTHONDONTWRITEBYTECODE=1 — Prevents .pyc file generation, reducing image size and avoiding stale bytecode issues.
  • PYTHONUNBUFFERED=1 — Ensures print() and logging output appears immediately in docker logs instead of being buffered.
  • --no-cache-dir — Tells pip not to store downloaded packages in cache, reducing the final image size.
  • libpq-dev vs libpq5 — Build stage needs development headers (-dev) for compiling psycopg2. Runtime only needs the shared library (libpq5).
  • Multi-stage build — Build tools (build-essential, gcc) exist only in the builder stage. The production image never contains them.

Common Modifications

  • FastAPI with Uvicorn: Replace CMD with CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "4"].
  • Poetry: Replace requirements.txt with COPY pyproject.toml poetry.lock ./ and RUN pip install poetry && poetry install --no-dev --no-interaction.
  • pip-tools: Use pip-compile requirements.in locally, then copy requirements.txt into the builder for deterministic installs.
  • Add static files: For Django, add RUN python manage.py collectstatic --noinput before switching to non-root user.
  • Development target: Add a FROM builder AS development stage that includes dev dependencies and runs with --reload flag.