Loading...
Loading...
Docker containerization patterns for Python/React projects. Use when creating or modifying Dockerfiles, optimizing image size, setting up Docker Compose for local development, or hardening container security. Covers multi-stage builds for Python (python:3.12-slim) and React (node:20-alpine -> nginx:alpine), layer optimization, .dockerignore, non-root user, security scanning with Trivy, Docker Compose for dev (backend + frontend + PostgreSQL + Redis), and image tagging strategy. Does NOT cover deployment orchestration (use deployment-pipeline).
npx skill4agent add hieutrtr/ai1-skills docker-best-practicesdeployment-pipelinepython-backend-expertreact-frontend-expert┌──────────────────────────────────┐
│ Stage 1: Builder │
│ Full SDK, build tools, deps │
│ Compile, install, build │
├──────────────────────────────────┤
│ Stage 2: Runtime │
│ Minimal base image │
│ COPY --from=builder artifacts │
│ Non-root user, health check │
└──────────────────────────────────┘references/python-dockerfile-template# Stage 1: Build dependencies
FROM python:3.12-slim AS builder
WORKDIR /app
RUN apt-get update && apt-get install -y --no-install-recommends \
build-essential libpq-dev \
&& rm -rf /var/lib/apt/lists/*
COPY requirements.txt .
RUN pip install --no-cache-dir --prefix=/install -r requirements.txt
# Stage 2: Runtime
FROM python:3.12-slim AS runtime
# Install only runtime system dependencies
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 appuser && useradd -r -g appuser -d /app -s /sbin/nologin appuser
WORKDIR /app
COPY /install /usr/local
COPY src/ ./src/
COPY alembic/ ./alembic/
COPY alembic.ini .
RUN chown -R appuser:appuser /app
USER appuser
EXPOSE 8000
HEALTHCHECK \
CMD curl -f http://localhost:8000/health || exit 1
CMD ["uvicorn", "src.main:app", "--host", "0.0.0.0", "--port", "8000"]python:3.12-slimalpine--no-cache-dir--prefix=/installlibpq5libpq-devcurlreferences/react-dockerfile-template# Stage 1: Build
FROM node:20-alpine AS builder
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --ignore-scripts
COPY . .
RUN npm run build
# Stage 2: Serve with Nginx
FROM nginx:alpine AS runtime
COPY /app/build /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
RUN addgroup -g 1001 -S appgroup && \
adduser -S appuser -u 1001 -G appgroup && \
chown -R appuser:appgroup /var/cache/nginx /var/log/nginx /etc/nginx/conf.d && \
touch /var/run/nginx.pid && chown appuser:appgroup /var/run/nginx.pid
USER appuser
EXPOSE 8080
HEALTHCHECK \
CMD wget -q --spider http://localhost:8080/ || exit 1
CMD ["nginx", "-g", "daemon off;"]node:20-alpinenginx:alpinenpm cinpm install--ignore-scripts| Use Case | Base Image | Size | Notes |
|---|---|---|---|
| Python backend | | ~150MB | Best compatibility with binary wheels |
| Python backend (minimal) | | ~50MB | May need musl workarounds for some packages |
| React build stage | | ~130MB | Only used during build |
| React runtime | | ~7MB | Production static file serving |
| Utility/scripts | | ~5MB | For helper containers |
latest-slim-alpine# 1. Base image (changes rarely)
FROM python:3.12-slim
# 2. System dependencies (changes monthly)
RUN apt-get update && apt-get install -y --no-install-recommends \
libpq5 curl && rm -rf /var/lib/apt/lists/*
# 3. Create user (changes never)
RUN groupadd -r appuser && useradd -r -g appuser appuser
# 4. Python dependencies (changes weekly)
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# 5. Application code (changes every commit)
COPY src/ ./src/
# 6. Runtime config (changes rarely)
USER appuser
EXPOSE 8000
CMD ["uvicorn", "src.main:app", "--host", "0.0.0.0", "--port", "8000"]# BAD: Copying everything before installing dependencies (busts cache)
COPY . .
RUN pip install -r requirements.txt
# GOOD: Copy only requirements first, then install, then copy code
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY src/ ./src/# BAD: Multiple RUN commands create multiple layers
RUN apt-get update
RUN apt-get install -y curl
RUN rm -rf /var/lib/apt/lists/*
# GOOD: Single RUN command, single layer, clean up in same layer
RUN apt-get update && apt-get install -y --no-install-recommends \
curl \
&& rm -rf /var/lib/apt/lists/*.dockerignore# Version control
.git
.gitignore
# Python
__pycache__
*.pyc
*.pyo
.pytest_cache
.mypy_cache
.ruff_cache
*.egg-info
dist/
build/
.venv/
venv/
# Node
node_modules/
npm-debug.log*
.next/
coverage/
# IDE
.vscode/
.idea/
*.swp
*.swo
# Docker
Dockerfile*
docker-compose*
.dockerignore
# Environment files
.env
.env.*
!.env.example
# Documentation
*.md
docs/
LICENSE
# CI/CD
.github/
.gitlab-ci.yml
# OS
.DS_Store
Thumbs.db.env# Create a dedicated user with no shell and no home directory
RUN groupadd -r appuser && \
useradd -r -g appuser -d /app -s /sbin/nologin appuser
# Set ownership of application files
COPY src/ ./src/
# Switch to non-root user
USER appuser# Install Trivy
curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh
# Scan image for vulnerabilities
trivy image --severity HIGH,CRITICAL app-backend:latest
# Scan and fail if HIGH/CRITICAL vulnerabilities found
trivy image --exit-code 1 --severity HIGH,CRITICAL app-backend:latest
# Generate JSON report
trivy image --format json --output trivy-report.json app-backend:latest
# Scan Dockerfile for misconfigurations
trivy config Dockerfile- name: Trivy vulnerability scan
uses: aquasecurity/trivy-action@master
with:
image-ref: 'app-backend:${{ github.sha }}'
format: 'sarif'
output: 'trivy-results.sarif'
severity: 'CRITICAL,HIGH'
exit-code: '1'# Read-only filesystem where possible
# (set at runtime with docker run --read-only)
# No new privileges
# (set at runtime with docker run --security-opt=no-new-privileges)
# Drop all capabilities, add only what is needed
# (set at runtime with docker run --cap-drop=ALL --cap-add=NET_BIND_SERVICE)
# Use COPY instead of ADD (ADD can auto-extract tarballs, security risk)
COPY requirements.txt . # GOOD
# ADD requirements.txt . # AVOID unless you need tar extractionreferences/docker-compose-template.yml┌────────────┐ ┌────────────┐
│ Frontend │ │ Backend │
│ React:3000 │───>│ FastAPI:8000│
└────────────┘ └─────┬──────┘
│
┌──────┴──────┐
│ │
┌────┴────┐ ┌────┴────┐
│PostgreSQL│ │ Redis │
│ :5432 │ │ :6379 │
└─────────┘ └─────────┘services:
backend:
build:
context: .
dockerfile: Dockerfile.backend
target: builder # Use builder stage for development (has dev tools)
volumes:
- ./src:/app/src # Hot reload
environment:
- DEBUG=true
- DATABASE_URL=postgresql+asyncpg://postgres:postgres@db:5432/app_dev
depends_on:
db:
condition: service_healthy
ports:
- "8000:8000"
frontend:
build:
context: ./frontend
target: builder
volumes:
- ./frontend/src:/app/src # Hot reload
ports:
- "3000:3000"
db:
image: postgres:16
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 5s
retries: 5
volumes:
- pgdata:/var/lib/postgresql/data
redis:
image: redis:7-alpine
healthcheck:
test: ["CMD", "redis-cli", "ping"]
volumes:
pgdata:depends_oncondition: service_healthytarget: builderregistry.example.com/app-backend:<tag>| Tag | When | Example | Purpose |
|---|---|---|---|
| Every build | | Immutable reference to exact code |
| Every push | | Latest from branch (mutable) |
| Release | | Semantic version release |
| Production deploy | | Current production (mutable) |
| Staging deploy | | Current staging (mutable) |
# Build with git SHA tag (immutable)
GIT_SHA=$(git rev-parse --short HEAD)
docker build -t "app-backend:git-${GIT_SHA}" .
# Tag for the branch
BRANCH=$(git rev-parse --abbrev-ref HEAD)
docker tag "app-backend:git-${GIT_SHA}" "app-backend:branch-${BRANCH}"
# Tag for release
docker tag "app-backend:git-${GIT_SHA}" "app-backend:v1.2.3"
# Tag as latest for production
docker tag "app-backend:git-${GIT_SHA}" "app-backend:latest"latestlatest# Add metadata labels
LABEL org.opencontainers.image.source="https://github.com/org/repo"
LABEL org.opencontainers.image.revision="${GIT_SHA}"
LABEL org.opencontainers.image.created="${BUILD_DATE}"# Build images
docker build -t app-backend:$(git rev-parse --short HEAD) -f Dockerfile.backend .
docker build -t app-frontend:$(git rev-parse --short HEAD) -f Dockerfile.frontend .
# Start local development
docker compose up -d && docker compose logs -f backend
# Scan for vulnerabilities
trivy image --severity HIGH,CRITICAL app-backend:latest
# Check image sizes
docker images --format "table {{.Repository}}\t{{.Tag}}\t{{.Size}}" | grep app-
# Clean up
docker image prune -f