Skip to main content
Advertisement

Docker Containerization

Containerize Python apps with optimized Dockerfiles, multi-stage builds, and Docker Compose.


Basic Dockerfile

# ── Basic example (before optimization) ──────────────────
FROM python:3.12-slim

WORKDIR /app

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

COPY . .

EXPOSE 8000
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]

Optimized Dockerfile (Multi-stage Build)

# ── Stage 1: Dependency build ─────────────────────────────
FROM python:3.12-slim AS builder

WORKDIR /app

# Install build tools (not included in final image)
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"

# Copy dependencies first (maximize layer caching)
COPY requirements.txt .
RUN pip install --no-cache-dir --upgrade pip && \
pip install --no-cache-dir -r requirements.txt

# ── Stage 2: Runtime image ────────────────────────────────
FROM python:3.12-slim AS runtime

# Security: run as non-root user
RUN groupadd --gid 1001 appgroup && \
useradd --uid 1001 --gid appgroup --shell /bin/bash --create-home appuser

WORKDIR /app

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

# Copy virtual environment from build stage
COPY --from=builder /opt/venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"

# Copy source code
COPY --chown=appuser:appgroup . .

# Switch to non-root user
USER appuser

EXPOSE 8000

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

CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "4"]

.dockerignore

# .dockerignore
__pycache__/
*.pyc
*.pyo
*.pyd
.Python
*.egg-info/
dist/
build/

# Development
.env
.env.*
!.env.example
.venv/
venv/

# Version control
.git/
.gitignore

# Tests / Docs
tests/
docs/
*.md
!README.md

# IDE
.vscode/
.idea/
*.swp

# OS
.DS_Store
Thumbs.db

# Build artifacts
*.log
logs/

Docker Compose — Local Development

# docker-compose.yml
version: "3.9"

services:
app:
build:
context: .
dockerfile: Dockerfile
target: runtime # target a specific stage of multi-stage build
ports:
- "8000:8000"
environment:
- DATABASE_URL=postgresql://postgres:password@db:5432/appdb
- REDIS_URL=redis://redis:6379/0
- DEBUG=true
env_file:
- .env.local # local overrides
volumes:
- .:/app # mount code during development
depends_on:
db:
condition: service_healthy
redis:
condition: service_started
restart: unless-stopped

db:
image: postgres:16-alpine
environment:
POSTGRES_DB: appdb
POSTGRES_USER: postgres
POSTGRES_PASSWORD: password
volumes:
- postgres_data:/var/lib/postgresql/data
- ./scripts/init.sql:/docker-entrypoint-initdb.d/init.sql
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 10s
timeout: 5s
retries: 5
ports:
- "5432:5432" # for local access during development

redis:
image: redis:7-alpine
volumes:
- redis_data:/data
command: redis-server --appendonly yes
ports:
- "6379:6379"

nginx:
image: nginx:alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
- ./nginx/ssl:/etc/nginx/ssl:ro
depends_on:
- app

volumes:
postgres_data:
redis_data:
# docker-compose.override.yml — development-only overrides
version: "3.9"

services:
app:
command: uvicorn main:app --host 0.0.0.0 --port 8000 --reload
environment:
- LOG_LEVEL=DEBUG

Common Docker Commands

# Build
docker build -t myapp:latest .
docker build --target builder -t myapp:dev .

# Run
docker run -d -p 8000:8000 --name myapp \
--env-file .env \
myapp:latest

# Docker Compose
docker compose up -d # start in background
docker compose up --build # build and start
docker compose down -v # stop and remove volumes
docker compose logs -f app # follow live logs
docker compose exec app bash # shell into container
docker compose ps # show service status

# Check image size
docker image inspect myapp:latest --format '{{.Size}}'
docker history myapp:latest

Image Size Comparison

python:3.12          ~1.0 GB  (default)
python:3.12-slim ~130 MB (lightweight)
python:3.12-alpine ~55 MB (minimal, no glibc — check compatibility)
multi-stage + slim ~150 MB (build tools excluded)

Summary

TechniqueEffect
-slim base image80% image size reduction
Multi-stage buildExcludes build tools from final image
.dockerignoreStrips unnecessary context
Separate dependency layerReuse cache when only code changes
Non-root userSecurity hardening
HEALTHCHECKContainer health monitoring
Advertisement