Skip to content

Docker Setup

Learn how to configure and run the FastAPI Boilerplate using Docker Compose. The project includes a complete containerized setup with PostgreSQL, Redis, background workers, and optional services.

Docker Compose Architecture

The boilerplate includes these core services:

services:
  web:          # FastAPI application (uvicorn or gunicorn)
  worker:       # ARQ background task worker  
  db:           # PostgreSQL 13 database
  redis:        # Redis Alpine for caching/queues
  # Optional services (commented out by default):
  # pgadmin:    # Database administration
  # nginx:      # Reverse proxy
  # create_superuser: # One-time superuser creation
  # create_tier:      # One-time tier creation

Basic Docker Compose

Main Configuration

The main docker-compose.yml includes:

version: '3.8'

services:
  web:
    build:
      context: .
      dockerfile: Dockerfile
    # Development mode (reload enabled)
    command: uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload
    # Production mode (uncomment for production)
    # command: gunicorn app.main:app -w 4 -k uvicorn.workers.UvicornWorker -b 0.0.0.0:8000
    env_file:
      - ./src/.env
    ports:
      - "8000:8000"
    depends_on:
      - db
      - redis
    volumes:
      - ./src/app:/code/app
      - ./src/.env:/code/.env

  worker:
    build:
      context: .
      dockerfile: Dockerfile
    command: arq app.core.worker.settings.WorkerSettings
    env_file:
      - ./src/.env
    depends_on:
      - db
      - redis
    volumes:
      - ./src/app:/code/app
      - ./src/.env:/code/.env

  db:
    image: postgres:13
    env_file:
      - ./src/.env
    volumes:
      - postgres-data:/var/lib/postgresql/data
    expose:
      - "5432"

  redis:
    image: redis:alpine
    volumes:
      - redis-data:/data
    expose:
      - "6379"

volumes:
  postgres-data:
  redis-data:

Environment File Loading

All services automatically load environment variables from ./src/.env:

env_file:
  - ./src/.env

The Docker services use these environment variables:

  • POSTGRES_USER, POSTGRES_PASSWORD, POSTGRES_DB for database
  • REDIS_*_HOST variables automatically resolve to service names
  • All application settings from your .env file

Service Details

Web Service (FastAPI Application)

The web service runs your FastAPI application:

web:
  build:
    context: .
    dockerfile: Dockerfile
  # Development: uvicorn with reload
  command: uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload
  # Production: gunicorn with multiple workers (commented out)
  # command: gunicorn app.main:app -w 4 -k uvicorn.workers.UvicornWorker -b 0.0.0.0:8000
  env_file:
    - ./src/.env
  ports:
    - "8000:8000"  # Direct access in development
  volumes:
    - ./src/app:/code/app  # Live code reloading
    - ./src/.env:/code/.env

Key Features:

  • Development mode: Uses uvicorn with --reload for automatic code reloading
  • Production mode: Switch to gunicorn with multiple workers (commented out)
  • Live reloading: Source code mounted as volume for development
  • Port exposure: Direct access on port 8000 (can be disabled for nginx)

Worker Service (Background Tasks)

Handles background job processing with ARQ:

worker:
  build:
    context: .
    dockerfile: Dockerfile
  command: arq app.core.worker.settings.WorkerSettings
  env_file:
    - ./src/.env
  depends_on:
    - db
    - redis
  volumes:
    - ./src/app:/code/app
    - ./src/.env:/code/.env

Features: - Runs ARQ worker for background job processing - Shares the same codebase and environment as web service - Automatically connects to Redis for job queues - Live code reloading in development

Database Service (PostgreSQL 13)

db:
  image: postgres:13
  env_file:
    - ./src/.env
  volumes:
    - postgres-data:/var/lib/postgresql/data
  expose:
    - "5432"  # Internal network only

Configuration: - Uses environment variables: POSTGRES_USER, POSTGRES_PASSWORD, POSTGRES_DB - Data persisted in named volume postgres-data - Only exposed to internal Docker network (no external port) - To enable external access, uncomment the ports section

Redis Service

redis:
  image: redis:alpine
  volumes:
    - redis-data:/data
  expose:
    - "6379"  # Internal network only

Features: - Lightweight Alpine Linux image - Data persistence with named volume - Used for caching, job queues, and rate limiting - Internal network access only

Optional Services

Database Administration (pgAdmin)

Uncomment to enable web-based database management:

pgadmin:
  container_name: pgadmin4
  image: dpage/pgadmin4:latest
  restart: always
  ports:
    - "5050:80"
  volumes:
    - pgadmin-data:/var/lib/pgadmin
  env_file:
    - ./src/.env
  depends_on:
    - db

Usage: - Access at http://localhost:5050 - Requires PGADMIN_DEFAULT_EMAIL and PGADMIN_DEFAULT_PASSWORD in .env - Connect to database using service name db and port 5432

Reverse Proxy (Nginx)

Uncomment for production-style reverse proxy:

nginx:
  image: nginx:latest
  ports:
    - "80:80"
  volumes:
    - ./default.conf:/etc/nginx/conf.d/default.conf
  depends_on:
    - web

Configuration: The included default.conf provides:

server {
    listen 80;

    location / {
        proxy_pass http://web:8000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

When using nginx:

  1. Uncomment the nginx service
  2. Comment out the ports section in the web service
  3. Uncomment expose: ["8000"] in the web service

Initialization Services

Create First Superuser

create_superuser:
  build:
    context: .
    dockerfile: Dockerfile
  env_file:
    - ./src/.env
  depends_on:
    - db
    - web
  command: python -m src.scripts.create_first_superuser
  volumes:
    - ./src:/code/src

Create First Tier

create_tier:
  build:
    context: .
    dockerfile: Dockerfile
  env_file:
    - ./src/.env
  depends_on:
    - db
    - web
  command: python -m src.scripts.create_first_tier
  volumes:
    - ./src:/code/src

Usage:

  • These are one-time setup services
  • Uncomment when you need to initialize data
  • Run once, then comment out again

Dockerfile Details

The project uses a multi-stage Dockerfile with uv for fast Python package management:

Builder Stage

FROM ghcr.io/astral-sh/uv:python3.11-bookworm-slim AS builder

ENV UV_COMPILE_BYTECODE=1
ENV UV_LINK_MODE=copy

WORKDIR /app

# Install dependencies (cached layer)
RUN --mount=type=cache,target=/root/.cache/uv \
    --mount=type=bind,source=uv.lock,target=uv.lock \
    --mount=type=bind,source=pyproject.toml,target=pyproject.toml \
    uv sync --locked --no-install-project

# Copy and install project
COPY . /app
RUN --mount=type=cache,target=/root/.cache/uv \
    uv sync --locked --no-editable

Final Stage

FROM python:3.11-slim-bookworm

# Create non-root user for security
RUN groupadd --gid 1000 app \
    && useradd --uid 1000 --gid app --shell /bin/bash --create-home app

# Copy virtual environment from builder
COPY --from=builder --chown=app:app /app/.venv /app/.venv

ENV PATH="/app/.venv/bin:$PATH"
USER app
WORKDIR /code

# Default command (can be overridden)
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000", "--reload"]

Security Features:

  • Non-root user execution
  • Multi-stage build for smaller final image
  • Cached dependency installation

Common Docker Commands

Development Workflow

# Start all services
docker compose up

# Start in background
docker compose up -d

# Rebuild and start (after code changes)
docker compose up --build

# View logs
docker compose logs -f web
docker compose logs -f worker

# Stop services
docker compose down

# Stop and remove volumes (reset data)
docker compose down -v

Service Management

# Start specific services
docker compose up web db redis

# Scale workers
docker compose up --scale worker=3

# Execute commands in running containers
docker compose exec web bash
docker compose exec db psql -U postgres
docker compose exec redis redis-cli

# View service status
docker compose ps

Production Mode

To switch to production mode:

  1. Enable Gunicorn:

    # Comment out uvicorn line
    # command: uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload
    # Uncomment gunicorn line
    command: gunicorn app.main:app -w 4 -k uvicorn.workers.UvicornWorker -b 0.0.0.0:8000
    

  2. Enable Nginx (optional):

    # Uncomment nginx service
    nginx:
      image: nginx:latest
      ports:
        - "80:80"
    
    # In web service, comment out ports and uncomment expose
    # ports:
    #   - "8000:8000"
    expose:
      - "8000"
    

  3. Remove development volumes:

    # Remove or comment out for production
    # volumes:
    #   - ./src/app:/code/app
    #   - ./src/.env:/code/.env
    

Environment Configuration

Service Communication

Services communicate using service names:

# In your .env file for Docker
POSTGRES_SERVER=db      # Not localhost
REDIS_CACHE_HOST=redis  # Not localhost
REDIS_QUEUE_HOST=redis
REDIS_RATE_LIMIT_HOST=redis

Port Management

Development (default): - Web: localhost:8000 (direct access) - Database: localhost:5432 (uncomment ports to enable) - Redis: localhost:6379 (uncomment ports to enable) - pgAdmin: localhost:5050 (if enabled)

Production with Nginx: - Web: localhost:80 (through nginx) - Database: Internal only - Redis: Internal only

Troubleshooting

Common Issues

Container won't start:

# Check logs
docker compose logs web

# Rebuild image
docker compose build --no-cache web

# Check environment file
docker compose exec web env | grep POSTGRES

Database connection issues:

# Check if db service is running
docker compose ps db

# Test connection from web container
docker compose exec web ping db

# Check database logs
docker compose logs db

Port conflicts:

# Check what's using the port
lsof -i :8000

# Use different ports
ports:
  - "8001:8000"  # Use port 8001 instead

Development vs Production

Development features:

  • Live code reloading with volume mounts
  • Direct port access
  • uvicorn with --reload
  • Exposed database/redis ports for debugging

Production optimizations:

  • No volume mounts (code baked into image)
  • Nginx reverse proxy
  • Gunicorn with multiple workers
  • Internal service networking only
  • Resource limits and health checks

Best Practices

Development

  • Use volume mounts for live code reloading
  • Enable direct port access for debugging
  • Use uvicorn with reload for fast development
  • Enable optional services (pgAdmin) as needed

Production

  • Switch to gunicorn with multiple workers
  • Use nginx for reverse proxy and load balancing
  • Remove volume mounts and bake code into images
  • Use internal networking only
  • Set resource limits and health checks

Security

  • Containers run as non-root user
  • Use internal networking for service communication
  • Don't expose database/redis ports externally
  • Use Docker secrets for sensitive data in production

Monitoring

  • Use docker compose logs to monitor services
  • Set up health checks for all services
  • Monitor resource usage with docker stats
  • Use structured logging for better observability

The Docker setup provides everything you need for both development and production. Start with the default configuration and customize as your needs grow!