161 lines
5.2 KiB
YAML
161 lines
5.2 KiB
YAML
version: "3.9"
|
|
|
|
# Production deployment with nginx as the public-facing web server.
|
|
# nginx terminates HTTP on port 80, serves static files and the frontend SPA
|
|
# directly, and proxies all backend routes to gunicorn.
|
|
#
|
|
# Quick start:
|
|
# cp .env.example .env.prod # fill in all values
|
|
# docker compose -f docker-compose.prod.yml up -d --build
|
|
# docker compose -f docker-compose.prod.yml exec web python manage.py migrate
|
|
# docker compose -f docker-compose.prod.yml exec web python manage.py createsuperuser
|
|
#
|
|
# For HTTPS: terminate TLS at nginx (add ssl_certificate / ssl_certificate_key
|
|
# directives to nginx/nginx.conf and map port 443) or put a reverse proxy such
|
|
# as Traefik in front and remove the port 80 mapping here.
|
|
|
|
services:
|
|
db:
|
|
image: postgres:16-alpine
|
|
restart: unless-stopped
|
|
environment:
|
|
POSTGRES_DB: training
|
|
POSTGRES_USER: training
|
|
POSTGRES_PASSWORD: ${DB_PASSWORD:?DB_PASSWORD is required}
|
|
volumes:
|
|
- postgres_data:/var/lib/postgresql/data
|
|
healthcheck:
|
|
test: ["CMD-SHELL", "pg_isready -U training"]
|
|
interval: 5s
|
|
timeout: 5s
|
|
retries: 10
|
|
networks:
|
|
- internal
|
|
|
|
redis:
|
|
image: redis:7-alpine
|
|
restart: unless-stopped
|
|
healthcheck:
|
|
test: ["CMD", "redis-cli", "ping"]
|
|
interval: 5s
|
|
timeout: 3s
|
|
retries: 10
|
|
networks:
|
|
- internal
|
|
|
|
web:
|
|
image: training-software:local
|
|
build:
|
|
context: .
|
|
restart: unless-stopped
|
|
env_file:
|
|
- .env
|
|
command: gunicorn config.wsgi:application --bind 0.0.0.0:8000 --workers 4 --timeout 60
|
|
volumes:
|
|
- staticfiles:/app/staticfiles
|
|
environment:
|
|
DJANGO_SETTINGS_MODULE: config.settings.prod
|
|
DJANGO_SECRET_KEY: ${DJANGO_SECRET_KEY:?DJANGO_SECRET_KEY is required}
|
|
DJANGO_ALLOWED_HOSTS: ${DJANGO_ALLOWED_HOSTS:?DJANGO_ALLOWED_HOSTS is required}
|
|
DATABASE_URL: "postgres://training:${DB_PASSWORD}@db:5432/training"
|
|
REDIS_URL: "redis://redis:6379/0"
|
|
# Set to false when nginx handles plain HTTP (no upstream HTTPS proxy).
|
|
# Set to true (or remove) when TLS is terminated at nginx or an upstream proxy.
|
|
SECURE_SSL_REDIRECT: ${SECURE_SSL_REDIRECT:-false}
|
|
OIDC_RP_CLIENT_ID: ${OIDC_RP_CLIENT_ID:-}
|
|
OIDC_RP_CLIENT_SECRET: ${OIDC_RP_CLIENT_SECRET:-}
|
|
OIDC_OP_AUTHORIZATION_ENDPOINT: ${OIDC_OP_AUTHORIZATION_ENDPOINT:-}
|
|
OIDC_OP_TOKEN_ENDPOINT: ${OIDC_OP_TOKEN_ENDPOINT:-}
|
|
OIDC_OP_USER_ENDPOINT: ${OIDC_OP_USER_ENDPOINT:-}
|
|
OIDC_OP_JWKS_ENDPOINT: ${OIDC_OP_JWKS_ENDPOINT:-}
|
|
depends_on:
|
|
db:
|
|
condition: service_healthy
|
|
redis:
|
|
condition: service_healthy
|
|
networks:
|
|
- internal
|
|
|
|
celery:
|
|
image: training-software:local
|
|
build:
|
|
context: .
|
|
restart: unless-stopped
|
|
env_file:
|
|
- .env
|
|
command: celery -A config worker --loglevel=info
|
|
environment:
|
|
DJANGO_SETTINGS_MODULE: config.settings.prod
|
|
DJANGO_SECRET_KEY: ${DJANGO_SECRET_KEY}
|
|
DJANGO_ALLOWED_HOSTS: ${DJANGO_ALLOWED_HOSTS}
|
|
DATABASE_URL: "postgres://training:${DB_PASSWORD}@db:5432/training"
|
|
REDIS_URL: "redis://redis:6379/0"
|
|
SECURE_SSL_REDIRECT: "false"
|
|
OIDC_RP_CLIENT_ID: ${OIDC_RP_CLIENT_ID:-}
|
|
OIDC_RP_CLIENT_SECRET: ${OIDC_RP_CLIENT_SECRET:-}
|
|
OIDC_OP_AUTHORIZATION_ENDPOINT: ${OIDC_OP_AUTHORIZATION_ENDPOINT:-}
|
|
OIDC_OP_TOKEN_ENDPOINT: ${OIDC_OP_TOKEN_ENDPOINT:-}
|
|
OIDC_OP_USER_ENDPOINT: ${OIDC_OP_USER_ENDPOINT:-}
|
|
OIDC_OP_JWKS_ENDPOINT: ${OIDC_OP_JWKS_ENDPOINT:-}
|
|
depends_on:
|
|
db:
|
|
condition: service_healthy
|
|
redis:
|
|
condition: service_healthy
|
|
networks:
|
|
- internal
|
|
|
|
celery-beat:
|
|
image: training-software:local
|
|
build:
|
|
context: .
|
|
restart: unless-stopped
|
|
env_file:
|
|
- .env
|
|
command: celery -A config beat --loglevel=info --scheduler django_celery_beat.schedulers:DatabaseScheduler
|
|
environment:
|
|
DJANGO_SETTINGS_MODULE: config.settings.prod
|
|
DJANGO_SECRET_KEY: ${DJANGO_SECRET_KEY}
|
|
DJANGO_ALLOWED_HOSTS: ${DJANGO_ALLOWED_HOSTS}
|
|
DATABASE_URL: "postgres://training:${DB_PASSWORD}@db:5432/training"
|
|
REDIS_URL: "redis://redis:6379/0"
|
|
SECURE_SSL_REDIRECT: "false"
|
|
OIDC_RP_CLIENT_ID: ${OIDC_RP_CLIENT_ID:-}
|
|
OIDC_RP_CLIENT_SECRET: ${OIDC_RP_CLIENT_SECRET:-}
|
|
OIDC_OP_AUTHORIZATION_ENDPOINT: ${OIDC_OP_AUTHORIZATION_ENDPOINT:-}
|
|
OIDC_OP_TOKEN_ENDPOINT: ${OIDC_OP_TOKEN_ENDPOINT:-}
|
|
OIDC_OP_USER_ENDPOINT: ${OIDC_OP_USER_ENDPOINT:-}
|
|
OIDC_OP_JWKS_ENDPOINT: ${OIDC_OP_JWKS_ENDPOINT:-}
|
|
depends_on:
|
|
db:
|
|
condition: service_healthy
|
|
redis:
|
|
condition: service_healthy
|
|
networks:
|
|
- internal
|
|
|
|
nginx:
|
|
image: nginx:1.27-alpine
|
|
restart: unless-stopped
|
|
environment:
|
|
FRONTEND_API_BASE_URL: ${FRONTEND_API_BASE_URL:-http://localhost:8000}
|
|
ports:
|
|
- "${HTTP_PORT:-80}:80"
|
|
volumes:
|
|
- ./nginx/nginx.conf:/etc/nginx/conf.d/default.conf:ro
|
|
- ./frontend/public:/usr/share/nginx/html
|
|
- ./frontend/docker-entrypoint/40-generate-config.sh:/docker-entrypoint.d/40-generate-config.sh:ro
|
|
- staticfiles:/app/staticfiles:ro
|
|
depends_on:
|
|
- web
|
|
networks:
|
|
- internal
|
|
|
|
volumes:
|
|
postgres_data:
|
|
staticfiles:
|
|
|
|
networks:
|
|
internal:
|
|
driver: bridge
|