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 aDockerfile 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 adocker-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"]
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