WarpBuild LogoWarpBuild Docs

Python with pip

Best practices for Dockerfile for Python with pip

🐳 Annotated Dockerfile for Python with pip:

# Start with a slim Python base image - 3.12 is recommended for modern features and performance
FROM python:3.12-slim-bookworm AS base
 
# Set environment variables
ENV PYTHONFAULTHANDLER=1 \
    PYTHONUNBUFFERED=1 \
    PYTHONDONTWRITEBYTECODE=1 \
    PYTHONHASHSEED=random \
    PIP_NO_CACHE_DIR=off \
    PIP_DISABLE_PIP_VERSION_CHECK=on \
    PIP_DEFAULT_TIMEOUT=100
 
# --------------------------------------
# Stage 1: Builder - installs dependencies and prepares app
# --------------------------------------
FROM base AS builder
 
# Install build dependencies
RUN apt-get update && apt-get install -y --no-install-recommends \
    build-essential \
    && rm -rf /var/lib/apt/lists/*
 
# Set up a virtual environment
RUN python -m venv /opt/venv
# Ensure we use the virtual environment
ENV PATH="/opt/venv/bin:$PATH"
 
# Set working directory
WORKDIR /app
 
# Copy and install requirements first for better caching
COPY requirements.txt .
COPY requirements-prod.txt .
 
# Install Python dependencies with caching
RUN --mount=type=cache,target=/root/.cache/pip \
    pip install -r requirements-prod.txt
 
# Copy the rest of the application code
COPY . .
 
# If you have a build step (e.g., compiling assets), run it here
RUN pip install --no-deps -e .
 
# --------------------------------------
# Stage 2: Final production image
# --------------------------------------
FROM base
 
# Create a non-root user and group
RUN groupadd -r appuser && useradd -r -g appuser appuser
 
# Set working directory
WORKDIR /app
 
# Copy only the virtual environment from the builder stage
COPY --from=builder /opt/venv /opt/venv
 
# Copy application code
COPY --from=builder /app /app
 
# Set environment variables to use virtual environment
ENV PATH="/opt/venv/bin:$PATH"
 
# Set ownership for application files
RUN chown -R appuser:appuser /app
 
# Switch to non-root user
USER appuser
 
# Expose the port your application runs on
EXPOSE 8000
 
# Run your application
CMD ["gunicorn", "--bind", "0.0.0.0:8000", "myapp.wsgi:application"]

🔍 Why these are best practices:

✅ Multi-stage builds

  • Efficiently separates the build environment from the runtime environment.
  • Dramatically reduces final image size by excluding build tools in the final image.
  • Improves security by minimizing the attack surface in your production container.

✅ Virtual environments

  • Isolates application dependencies from system Python packages.
  • Ensures consistent environment for your application.
  • Makes it easier to manage dependency conflicts.

✅ Dependency caching

  • Uses Docker's build cache to avoid redundant pip downloads.
  • Dramatically speeds up build time, especially in CI/CD environments.
  • Reduces network usage and build time.

✅ Environment variable optimization

  • PYTHONDONTWRITEBYTECODE=1 avoids creating .pyc files, reducing image size.
  • PYTHONUNBUFFERED=1 ensures Python output is sent straight to the container logs.
  • PIP_DISABLE_PIP_VERSION_CHECK=on eliminates unnecessary version checks.

✅ Non-root user

  • Runs application as non-privileged user for enhanced security.
  • Follows principle of least privilege to reduce risk of container escape.
  • Required in many enterprise Kubernetes environments.

🚀 Additional Dockerfile best practices you can adopt:

Split requirements for dev and prod

Maintain separate requirements files for different environments:

# requirements.txt - Base requirements
flask==2.3.2
sqlalchemy==2.0.16
pydantic==2.0.2

# requirements-dev.txt - Development requirements
-r requirements.txt
pytest==7.3.1
black==23.3.0
flake8==6.0.0

# requirements-prod.txt - Production requirements
-r requirements.txt
gunicorn==20.1.0

Add a health check

Monitor the health of your container and enable automatic recovery:

HEALTHCHECK --interval=30s --timeout=3s \
  CMD curl -f http://localhost:8000/health || exit 1

Use .dockerignore

Exclude unnecessary files from your Docker build context:

__pycache__/
*.py[cod]
*$py.class
.env
.venv
env/
venv/
ENV/
.pytest_cache/
.coverage
htmlcov/
.git/
.github/
.idea/
.vscode/
*.md
!README.md

Pin exact dependency versions

For deterministic builds, pin exact versions in your requirements.txt:

# Good - pinned exact versions
flask==2.3.2
sqlalchemy==2.0.16

# Bad - allows minor or patch version changes
flask>=2.3.0
sqlalchemy~=2.0.0

Consider using a dedicated Python app server

Use a production-ready (Web Server Gateway Interface) server instead of development servers:

# Install production server
RUN pip install gunicorn
 
# Use in your CMD
CMD ["gunicorn", "--workers", "4", "--bind", "0.0.0.0:8000", "app:app"]

Pre-compile Python code

For slightly faster startup, pre-compile your Python modules:

# Pre-compile Python bytecode
RUN python -m compileall /app

By following these practices, you'll create Docker images for your Python applications that are efficient, secure, and optimized for both development and production environments.

Last updated on