DEV Community

Borino88
Borino88

Posted on

A Practical Multi-Stage Docker Build for Python APIs

When containerizing Python web applications for production, developers often produce heavy, insecure images containing compilers, headers, and root execution privileges.

This post demonstrates how to build a hardened, multi-stage Docker container utilizing a non-root runtime environment, health check verification, and OCI image metadata.

The Production Dockerfile

This multi-stage Dockerfile uses python:3.11-slim-bookworm to separate building dependencies from runtime execution:

# ==============================================================================
# Stage 1: Build & Dependency Packaging
# ==============================================================================
FROM python:3.11-slim-bookworm AS builder

WORKDIR /build

RUN apt-get update && apt-get install -y --no-install-recommends \
    build-essential \
    && rm -rf /var/lib/apt/lists/*

COPY requirements.txt .
RUN pip install --no-cache-dir --prefix=/install -r requirements.txt

# ==============================================================================
# Stage 2: Production Runtime
# ==============================================================================
FROM python:3.11-slim-bookworm AS runtime

# OCI Standard Metadata Labels
LABEL org.opencontainers.image.title="Secure API Platform"
LABEL org.opencontainers.image.source="https://github.com/Borino88/secure-api-platform"
LABEL org.opencontainers.image.version="1.0.0"
LABEL org.opencontainers.image.licenses="MIT"
LABEL org.opencontainers.image.authors="Mahdi Fattahi <a.borino88@gmail.com>"

ENV PYTHONUNBUFFERED=1 \
    PYTHONDONTWRITEBYTECODE=1 \
    PATH="/usr/local/lib/python3.11/site-packages:/app:$PATH" \
    PYTHONPATH="/app"

RUN groupadd -g 1000 appgroup && \
    useradd -u 1000 -g appgroup -s /bin/bash -m appuser

WORKDIR /app

COPY --from=builder /install /usr/local
COPY requirements.txt ./
COPY src/ ./src/

RUN chown -R appuser:appgroup /app

USER appuser

HEALTHCHECK --interval=30s --timeout=5s --start-period=5s --retries=3 \
  CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/health').getcode() == 200" || exit 1

EXPOSE 8000

CMD ["uvicorn", "src.api.main:app", "--host", "0.0.0.0", "--port", "8000"]
Enter fullscreen mode Exit fullscreen mode

Security Best Practices Highlighted

  1. Multi-Stage Separation: All compiler tools and cached wheels remain in Stage 1, leaving Stage 2 clean.
  2. Non-Root Execution: We define appuser and appgroup to prevent root execution attacks.
  3. Health Checks: A native, dependency-free Python health check endpoint verification command is integrated directly.
  4. Vulnerability Mitigation: Hardened bases limit base image CVE counts, validated using Trivy scans.

The configuration templates are available in the public secure-api-platform repository.


Disclosure: This article was prepared with AI-assisted editing and research support. I reviewed the technical content, tested the code and take responsibility for the final article. #ABotWroteThis

Top comments (0)