Skip to main content
Deploy FootyCollect to production using Docker Compose with a complete stack including Django, PostgreSQL, Redis, Traefik (reverse proxy with SSL), Celery workers, and AWS CLI for backups.

Overview

The Docker production deployment uses docker-compose.production.yml to orchestrate all services:
  • Django + Gunicorn: Application server
  • PostgreSQL: Database with persistent volumes
  • Redis: Cache and Celery message broker
  • Traefik: Reverse proxy with automatic SSL via Let’s Encrypt
  • Celery Worker: Background task processing
  • Celery Beat: Scheduled task scheduler
  • Flower: Celery monitoring (optional)
  • AWS CLI: Database backup automation
All static and media files are served from S3/R2 in production. The collectstatic command runs automatically during container startup.

Prerequisites

1

Install Docker

Install Docker Engine and Docker Compose v2+:
# Ubuntu/Debian
curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.sh
sudo usermod -aG docker $USER

# Verify installation
docker --version
docker compose version
2

Domain and DNS

  • Register a domain name
  • Point DNS A records to your server’s IP:
    • yourdomain.comyour.server.ip
    • www.yourdomain.comyour.server.ip
3

Firewall Configuration

Open required ports:
sudo ufw allow ssh
sudo ufw allow 80/tcp    # HTTP (Traefik)
sudo ufw allow 443/tcp   # HTTPS (Traefik)
sudo ufw enable

Deployment

1. Clone Repository

cd /opt
sudo git clone https://github.com/sunr4y/FootyCollect.git footycollect
cd footycollect
Build context must be the repository root. All Docker Compose commands must run from /opt/footycollect.

2. Configure Environment Files

Create production environment files by copying the template:
# Create environment directories
mkdir -p .envs/.production

# Copy template (reference)
cp deploy/env.example .envs/.production/env.reference
Create two environment files:
Django application environment variables:
.envs/.production/.django
# Django Settings
DJANGO_SECRET_KEY=your-secret-key-here
DJANGO_DEBUG=False
DJANGO_ALLOWED_HOSTS=yourdomain.com,www.yourdomain.com
DJANGO_ADMIN_URL=admin/

# Security
DJANGO_SECURE_SSL_REDIRECT=True
DJANGO_SECURE_HSTS_INCLUDE_SUBDOMAINS=True
DJANGO_SECURE_HSTS_PRELOAD=True

# Redis (use service name from docker-compose)
REDIS_URL=redis://redis:6379/0

# Email (SendGrid)
SENDGRID_API_KEY=your-sendgrid-api-key
DJANGO_DEFAULT_FROM_EMAIL=noreply@yourdomain.com

# Sentry
SENTRY_DSN=your-sentry-dsn
SENTRY_ENVIRONMENT=production

# Storage (AWS S3)
STORAGE_BACKEND=aws
DJANGO_AWS_ACCESS_KEY_ID=your-access-key
DJANGO_AWS_SECRET_ACCESS_KEY=your-secret-key
DJANGO_AWS_STORAGE_BUCKET_NAME=your-bucket
DJANGO_AWS_S3_REGION_NAME=us-east-1

# Or Cloudflare R2
# STORAGE_BACKEND=r2
# CLOUDFLARE_ACCESS_KEY_ID=your-access-key
# CLOUDFLARE_SECRET_ACCESS_KEY=your-secret-key
# CLOUDFLARE_BUCKET_NAME=your-bucket
# CLOUDFLARE_R2_ENDPOINT_URL=https://account-id.r2.cloudflarestorage.com

# FKAPI (optional)
FKA_API_IP=your-fkapi-server
API_KEY=your-api-key

# Content Security Policy
DJANGO_CSP_ENABLED=True
DJANGO_CSP_IMG_SRC='self', data:, blob:, https://www.gravatar.com, https://cdn.footballkitarchive.com, https://your-bucket.s3.amazonaws.com
Generate SECRET_KEY: python -c 'from django.core.management.utils import get_random_secret_key; print(get_random_secret_key())'
See the environment setup guide for detailed variable descriptions.

3. Configure Traefik

Create Traefik configuration for SSL certificates:
mkdir -p compose/production/traefik
Edit compose/production/traefik/traefik.yml and update the domain and email:
compose/production/traefik/traefik.yml
log:
  level: INFO

entryPoints:
  web:
    address: ":80"
    http:
      redirections:
        entryPoint:
          to: web-secure
          scheme: https
  web-secure:
    address: ":443"

certificatesResolvers:
  letsencrypt:
    acme:
      email: your-email@yourdomain.com
      storage: /etc/traefik/acme/acme.json
      httpChallenge:
        entryPoint: web

http:
  routers:
    web-secure-router:
      rule: "Host(`yourdomain.com`) || Host(`www.yourdomain.com`)"
      entryPoints:
        - web-secure
      service: django
      tls:
        certResolver: letsencrypt

  services:
    django:
      loadBalancer:
        servers:
          - url: http://django:5000

providers:
  file:
    filename: /etc/traefik/traefik.yml
    watch: true

4. Build Images

Build all production Docker images:
# Build from repository root
docker compose -f docker-compose.production.yml build
This builds:
  • footycollect_production_django (multi-stage build from compose/production/django/Dockerfile)
  • footycollect_production_postgres
  • footycollect_production_traefik
  • footycollect_production_celeryworker
  • footycollect_production_celerybeat
  • footycollect_production_flower

5. Start Services

# Start all services in detached mode
docker compose -f docker-compose.production.yml up -d

# View logs
docker compose -f docker-compose.production.yml logs -f

# View specific service logs
docker compose -f docker-compose.production.yml logs -f django
On first startup, Django will automatically:
  1. Wait for PostgreSQL to be ready
  2. Run database migrations
  3. Collect static files to S3/R2
  4. Start Gunicorn

6. Create Superuser

Create an admin user:
docker compose -f docker-compose.production.yml run --rm django python manage.py createsuperuser

7. Verify Deployment

Check that all services are running:
# Check service status
docker compose -f docker-compose.production.yml ps

# Test health endpoint
curl https://yourdomain.com/health/

# Test ready endpoint (includes DB check)
curl https://yourdomain.com/ready/

# Run Django deployment checks
docker compose -f docker-compose.production.yml run --rm django python manage.py check --deploy

Service Management

Starting and Stopping

# Start all services
docker compose -f docker-compose.production.yml up -d

# Stop all services
docker compose -f docker-compose.production.yml down

# Restart specific service
docker compose -f docker-compose.production.yml restart django

# Stop and remove volumes (WARNING: deletes data)
docker compose -f docker-compose.production.yml down -v

Viewing Logs

# All services
docker compose -f docker-compose.production.yml logs -f

# Django only
docker compose -f docker-compose.production.yml logs -f django

# Celery worker
docker compose -f docker-compose.production.yml logs -f celeryworker

# Last 100 lines
docker compose -f docker-compose.production.yml logs --tail=100 django

Running Management Commands

# Run migrations
docker compose -f docker-compose.production.yml run --rm django python manage.py migrate

# Collect static files
docker compose -f docker-compose.production.yml run --rm django python manage.py collectstatic --noinput

# Create superuser
docker compose -f docker-compose.production.yml run --rm django python manage.py createsuperuser

# Django shell
docker compose -f docker-compose.production.yml run --rm django python manage.py shell

# Custom management command
docker compose -f docker-compose.production.yml run --rm django python manage.py your_command

Database Management

Backups

The awscli service can backup PostgreSQL to S3:
# Manual backup to S3
docker compose -f docker-compose.production.yml run --rm awscli /backup

# Local backup
docker compose -f docker-compose.production.yml exec postgres backup

# List backups
docker compose -f docker-compose.production.yml exec postgres backups
Backups are stored in the production_postgres_data_backups volume.

Restore

# List available backups
docker compose -f docker-compose.production.yml exec postgres backups

# Restore from backup
docker compose -f docker-compose.production.yml exec postgres restore backup_2026_03_02.sql.gz
Restoring a backup will overwrite the current database. Always create a backup before restoring.

Static and Media Files

Static and media files are served from S3/R2 in production.

Collect Static Files

# Collect and upload to S3/R2
docker compose -f docker-compose.production.yml run --rm django python manage.py collectstatic --noinput

# Clear existing files before collecting
docker compose -f docker-compose.production.yml run --rm django python manage.py collectstatic --noinput --clear

# Verify static files configuration
docker compose -f docker-compose.production.yml run --rm django python manage.py verify_staticfiles

Storage Configuration

Configure storage backend in .envs/.production/.django:
STORAGE_BACKEND=aws
DJANGO_AWS_ACCESS_KEY_ID=your-access-key
DJANGO_AWS_SECRET_ACCESS_KEY=your-secret-key
DJANGO_AWS_STORAGE_BUCKET_NAME=your-bucket
DJANGO_AWS_S3_REGION_NAME=us-east-1
DJANGO_AWS_S3_CUSTOM_DOMAIN=cdn.yourdomain.com  # Optional CDN
STORAGE_BACKEND=r2
CLOUDFLARE_ACCESS_KEY_ID=your-access-key
CLOUDFLARE_SECRET_ACCESS_KEY=your-secret-key
CLOUDFLARE_BUCKET_NAME=your-bucket
CLOUDFLARE_R2_ENDPOINT_URL=https://account-id.r2.cloudflarestorage.com
CLOUDFLARE_R2_CUSTOM_DOMAIN=cdn.yourdomain.com  # Optional
R2 requires CORS configuration if serving fonts or assets from a custom domain. See deploy/r2-cors-wrangler.json in the repository.

Updating the Application

1

Pull Latest Code

cd /opt/footycollect
sudo git pull origin main
2

Rebuild Images

docker compose -f docker-compose.production.yml build
3

Run Migrations

docker compose -f docker-compose.production.yml run --rm django python manage.py migrate
4

Collect Static Files

docker compose -f docker-compose.production.yml run --rm django python manage.py collectstatic --noinput
5

Restart Services

docker compose -f docker-compose.production.yml up -d
6

Verify Deployment

# Check health
curl https://yourdomain.com/health/

# Run deployment checks
docker compose -f docker-compose.production.yml run --rm django python manage.py check --deploy

Monitoring

Flower (Celery Monitoring)

Access Flower at https://yourdomain.com:5555 (if port 5555 is exposed).
Secure Flower with authentication in production. Do not expose it publicly without protection.

Container Health

# Check container health
docker compose -f docker-compose.production.yml ps

# View resource usage
docker stats

# Inspect specific container
docker inspect footycollect_production_django

Application Health

# Health check endpoint
curl https://yourdomain.com/health/

# Readiness check (includes DB)
curl https://yourdomain.com/ready/

# Django checks
docker compose -f docker-compose.production.yml run --rm django python manage.py check --deploy

Troubleshooting

Container Won’t Start

docker compose -f docker-compose.production.yml logs -f django
docker compose -f docker-compose.production.yml logs -f postgres
Verify PostgreSQL environment variables match in both files:
  • .envs/.production/.postgres
  • .envs/.production/.django (DATABASE_URL)
Check PostgreSQL is ready:
docker compose -f docker-compose.production.yml exec postgres pg_isready
Ensure both environment files exist:
ls -la .envs/.production/
# Should show: .django and .postgres
Verify no syntax errors:
cat .envs/.production/.django | grep -v '^#' | grep -v '^$'

SSL Certificate Issues

Check Traefik logs:
docker compose -f docker-compose.production.yml logs -f traefik
Verify DNS is pointing to your server:
dig yourdomain.com
Ensure port 80 is accessible for HTTP challenge:
curl http://yourdomain.com/.well-known/acme-challenge/test
Remove existing certificates and restart:
docker compose -f docker-compose.production.yml down
sudo rm -rf compose/production/traefik/acme/acme.json
docker compose -f docker-compose.production.yml up -d

Static Files Not Loading

Verify storage configuration:
docker compose -f docker-compose.production.yml run --rm django python manage.py verify_staticfiles
Test S3/R2 connectivity:
docker compose -f docker-compose.production.yml run --rm django python manage.py check --deploy
If using Cloudflare R2 with custom domain, configure CORS:See deploy/r2-cors-wrangler.json for CORS policy. Apply with:
npx wrangler r2 bucket cors set your-bucket --file deploy/r2-cors-wrangler.json

Performance Issues

Edit compose/production/django/start:
gunicorn --workers 4 --bind 0.0.0.0:5000 config.wsgi:application
Recommended workers: (2 * CPU cores) + 1
docker stats
Add resource limits in docker-compose.production.yml:
services:
  django:
    deploy:
      resources:
        limits:
          cpus: '2'
          memory: 2G

Security Hardening

Complete the production checklist before deploying to production.

Firewall Rules

# Allow only necessary ports
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow ssh
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable

Docker Security

# Run containers as non-root (already configured in Dockerfiles)
# Enable Docker security scanning
docker scan footycollect_production_django

# Keep Docker updated
sudo apt-get update
sudo apt-get install docker-ce docker-ce-cli containerd.io

Environment Security

  • Never commit .envs/.production/ to version control
  • Use strong passwords for POSTGRES_PASSWORD and SECRET_KEY
  • Rotate SECRET_KEY and database credentials regularly
  • Restrict access to environment files:
    chmod 600 .envs/.production/.django
    chmod 600 .envs/.production/.postgres
    

Next Steps