Skip to main content

Overview

Docker provides a consistent, reproducible way to deploy Framefox applications across different environments. This guide covers containerization best practices for Framefox.

Basic Dockerfile

Create a Dockerfile in your project root:
# Use official Python runtime as base image
FROM python:3.12-slim

# Set working directory
WORKDIR /app

# Install system dependencies
RUN apt-get update && apt-get install -y \
    gcc \
    postgresql-client \
    && rm -rf /var/lib/apt/lists/*

# Copy requirements file
COPY requirements.txt .

# Install Python dependencies
RUN pip install --no-cache-dir -r requirements.txt

# Copy application code
COPY . .

# Create necessary directories
RUN mkdir -p var/cache var/log var/session

# Create non-root user
RUN useradd -m -u 1000 appuser && chown -R appuser:appuser /app
USER appuser

# Expose port
EXPOSE 8000

# Run the application
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]

Multi-Stage Build

Optimize image size with multi-stage builds:
# Build stage
FROM python:3.12-slim AS builder

WORKDIR /app

# Install build dependencies
RUN apt-get update && apt-get install -y \
    gcc \
    g++ \
    libpq-dev \
    && rm -rf /var/lib/apt/lists/*

# Install Python dependencies
COPY requirements.txt .
RUN pip install --no-cache-dir --user -r requirements.txt

# Runtime stage
FROM python:3.12-slim

WORKDIR /app

# Install runtime dependencies
RUN apt-get update && apt-get install -y \
    postgresql-client \
    && rm -rf /var/lib/apt/lists/*

# Copy Python dependencies from builder
COPY --from=builder /root/.local /root/.local

# Copy application code
COPY . .

# Create necessary directories
RUN mkdir -p var/cache var/log var/session

# Create non-root user
RUN useradd -m -u 1000 appuser && chown -R appuser:appuser /app
USER appuser

# Update PATH
ENV PATH=/root/.local/bin:$PATH

# Expose port
EXPOSE 8000

# Health check
HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \
    CMD python -c "import requests; requests.get('http://localhost:8000/health')"

# Run the application
CMD ["gunicorn", "main:app", \
     "--workers", "4", \
     "--worker-class", "uvicorn.workers.UvicornWorker", \
     "--bind", "0.0.0.0:8000"]

.dockerignore

Create a .dockerignore file to exclude unnecessary files:
# Python
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
venv/
env/
ENV/

# Framefox
var/cache/
var/log/
var/session/

# Environment
.env
.env.local

# IDE
.vscode/
.idea/
*.swp
*.swo

# Git
.git/
.gitignore

# Documentation
README.md
DOCS.md

# Tests
tests/
pytest.ini
.pytest_cache/

# Docker
Dockerfile
docker-compose.yml
.dockerignore

Docker Compose Setup

Create a docker-compose.yml for local development and testing:
version: '3.8'

services:
  web:
    build:
      context: .
      dockerfile: Dockerfile
    ports:
      - "8000:8000"
    environment:
      - APP_ENV=dev
      - DATABASE_URL=postgresql://framefox:password@db:5432/framefox
      - REDIS_URL=redis://redis:6379/0
    volumes:
      - .:/app
      - /app/var/cache
      - /app/var/log
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_started
    command: uvicorn main:app --host 0.0.0.0 --port 8000 --reload

  db:
    image: postgres:16-alpine
    environment:
      - POSTGRES_USER=framefox
      - POSTGRES_PASSWORD=password
      - POSTGRES_DB=framefox
    volumes:
      - postgres_data:/var/lib/postgresql/data
    ports:
      - "5432:5432"
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U framefox"]
      interval: 10s
      timeout: 5s
      retries: 5

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

volumes:
  postgres_data:
  redis_data:

Production Docker Compose

For production deployments:
version: '3.8'

services:
  web:
    build:
      context: .
      dockerfile: Dockerfile
      target: runtime
    restart: unless-stopped
    ports:
      - "8000:8000"
    environment:
      - APP_ENV=prod
      - DATABASE_URL=${DATABASE_URL}
      - REDIS_URL=${REDIS_URL}
      - SECRET_KEY=${SECRET_KEY}
      - SENTRY_DSN=${SENTRY_DSN}
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_started
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"

  db:
    image: postgres:16-alpine
    restart: unless-stopped
    environment:
      - POSTGRES_USER=${DB_USER}
      - POSTGRES_PASSWORD=${DB_PASSWORD}
      - POSTGRES_DB=${DB_NAME}
    volumes:
      - postgres_data:/var/lib/postgresql/data
      - ./backups:/backups
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${DB_USER}"]
      interval: 10s
      timeout: 5s
      retries: 5

  redis:
    image: redis:7-alpine
    restart: unless-stopped
    volumes:
      - redis_data:/data
    command: redis-server --appendonly yes --requirepass ${REDIS_PASSWORD}
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 5s
      retries: 5

  nginx:
    image: nginx:alpine
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
      - ./public:/var/www/public:ro
      - ./ssl:/etc/nginx/ssl:ro
    depends_on:
      - web

volumes:
  postgres_data:
  redis_data:

Building and Running

Build Image

docker build -t framefox-app:latest .

Run Container

docker run -d \
  --name framefox \
  -p 8000:8000 \
  -e APP_ENV=prod \
  -e DATABASE_URL=postgresql://user:pass@host/db \
  framefox-app:latest

Using Docker Compose

# Development
docker-compose up

# Production (detached)
docker-compose -f docker-compose.yml up -d

# View logs
docker-compose logs -f web

# Stop services
docker-compose down

# Stop and remove volumes
docker-compose down -v

Database Migrations in Docker

Run Migrations

# During build
docker-compose run --rm web framefox database upgrade

# In running container
docker-compose exec web framefox database upgrade

Create Migration

docker-compose exec web framefox database create-migration "add user table"

Migration in Dockerfile

Add migration to startup:
# Create entrypoint script
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh

ENTRYPOINT ["/entrypoint.sh"]
Create entrypoint.sh:
#!/bin/bash
set -e

# Run migrations
echo "Running database migrations..."
framefox database upgrade

# Start application
exec "$@"

Container Optimization

Layer Caching

Optimize layer caching by copying dependencies first:
# Copy only requirements first (cached if unchanged)
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# Copy application code (changes frequently)
COPY . .

Smaller Images

Use Alpine-based images for smaller size:
FROM python:3.12-alpine

# Install runtime dependencies
RUN apk add --no-cache postgresql-client

Security Scanning

Scan images for vulnerabilities:
# Using Trivy
trivy image framefox-app:latest

# Using Docker Scout
docker scout cves framefox-app:latest

Environment Variables in Docker

.env File

Create .env file for Docker Compose:
APP_ENV=prod
DATABASE_URL=postgresql://framefox:password@db:5432/framefox
REDIS_URL=redis://redis:6379/0
SECRET_KEY=your-secret-key-here
SENTRY_DSN=https://[email protected]/project

# Database
DB_USER=framefox
DB_PASSWORD=secure-password
DB_NAME=framefox

# Redis
REDIS_PASSWORD=redis-password

Docker Secrets

For Docker Swarm, use secrets:
services:
  web:
    secrets:
      - db_password
      - secret_key
    environment:
      - DATABASE_URL=postgresql://user:$(cat /run/secrets/db_password)@db/dbname

secrets:
  db_password:
    external: true
  secret_key:
    external: true

Volumes and Persistence

Named Volumes

services:
  web:
    volumes:
      - app_data:/app/var

volumes:
  app_data:

Bind Mounts (Development)

services:
  web:
    volumes:
      - .:/app  # Mount current directory
      - /app/var/cache  # Anonymous volume for cache

Networking

Custom Network

services:
  web:
    networks:
      - frontend
      - backend

  db:
    networks:
      - backend

networks:
  frontend:
  backend:
    internal: true  # No external access

Health Checks

Application Health Check

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

Custom Health Endpoint

from framefox.core.routing.decorator.route import Route
from framefox.core.controller.abstract_controller import AbstractController

class HealthController(AbstractController):
    
    @Route("/health", "health.check", methods=["GET"])
    async def check(self):
        # Check database connectivity
        try:
            await self.db.execute("SELECT 1")
            db_status = "healthy"
        except Exception:
            db_status = "unhealthy"
        
        return {
            "status": "healthy" if db_status == "healthy" else "degraded",
            "database": db_status,
            "version": "1.0.0"
        }

Docker Registry

Tag and Push

# Tag image
docker tag framefox-app:latest registry.example.com/framefox-app:1.0.0

# Push to registry
docker push registry.example.com/framefox-app:1.0.0

# Pull on production server
docker pull registry.example.com/framefox-app:1.0.0

GitHub Container Registry

# Login
echo $GITHUB_TOKEN | docker login ghcr.io -u USERNAME --password-stdin

# Tag
docker tag framefox-app:latest ghcr.io/username/framefox-app:latest

# Push
docker push ghcr.io/username/framefox-app:latest

CI/CD Integration

GitHub Actions Example

name: Build and Deploy

on:
  push:
    branches: [main]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      
      - name: Build Docker image
        run: docker build -t framefox-app:${{ github.sha }} .
      
      - name: Run tests
        run: docker run framefox-app:${{ github.sha }} pytest
      
      - name: Push to registry
        run: |
          echo ${{ secrets.DOCKER_PASSWORD }} | docker login -u ${{ secrets.DOCKER_USERNAME }} --password-stdin
          docker tag framefox-app:${{ github.sha }} username/framefox-app:latest
          docker push username/framefox-app:latest

Troubleshooting

View Logs

# All services
docker-compose logs -f

# Specific service
docker-compose logs -f web

# Last 100 lines
docker-compose logs --tail=100 web

Execute Commands

# Run command in running container
docker-compose exec web framefox debug router

# Start shell
docker-compose exec web bash

# Run one-off command
docker-compose run --rm web python script.py

Inspect Container

# View container details
docker inspect framefox

# Check resource usage
docker stats framefox

Next Steps

Production Deployment

Deploy to production servers

Environment Configuration

Manage environment variables

Build docs developers (and LLMs) love