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