services: db: image: postgres:16.9-alpine restart: unless-stopped init: true environment: POSTGRES_DB: ${POSTGRES_DB:-theatre_lab} POSTGRES_USER: ${POSTGRES_USER:-theatre_lab} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-replace-with-a-local-password} volumes: - postgres_data:/var/lib/postgresql/data networks: default: aliases: - postgres healthcheck: test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"] interval: 5s timeout: 5s retries: 10 start_period: 5s security_opt: - no-new-privileges:true backend: build: context: ./backend restart: unless-stopped init: true environment: DATABASE_URL: ${DATABASE_URL:-postgresql://theatre_lab:replace-with-a-local-password@db:5432/theatre_lab} DJANGO_SECRET_KEY: ${DJANGO_SECRET_KEY:-replace-with-a-long-random-development-key} DJANGO_DEBUG: ${DJANGO_DEBUG:-true} DJANGO_ALLOWED_HOSTS: ${DJANGO_ALLOWED_HOSTS:-localhost,127.0.0.1,backend,azionelab.org} WAGTAILADMIN_BASE_URL: ${WAGTAILADMIN_BASE_URL:-http://azionelab.org:8080} volumes: - media_data:/app/media ports: - "127.0.0.1:8000:8000" depends_on: db: condition: service_healthy healthcheck: test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8000/health/', timeout=2)"] interval: 10s timeout: 5s retries: 10 start_period: 20s security_opt: - no-new-privileges:true frontend: build: context: ./frontend restart: unless-stopped init: true environment: PUBLIC_CMS_API_URL: ${PUBLIC_CMS_API_URL:-http://backend:8000} ports: - "127.0.0.1:4321:4321" depends_on: backend: condition: service_healthy healthcheck: test: [ "CMD", "node", "-e", "fetch('http://localhost:4321/favicon.svg').then(r=>{if(!r.ok)process.exit(1)}).catch(()=>process.exit(1))", ] interval: 10s timeout: 5s retries: 10 start_period: 20s security_opt: - no-new-privileges:true proxy: build: context: ./nginx restart: unless-stopped init: true environment: LETSENCRYPT_ENABLED: ${LETSENCRYPT_ENABLED:-0} LETSENCRYPT_DOMAIN: ${LETSENCRYPT_DOMAIN:-azionelab.org} TLS_RELOAD_INTERVAL_SECONDS: ${TLS_RELOAD_INTERVAL_SECONDS:-30} volumes: - letsencrypt_data:/etc/letsencrypt:ro - certbot_challenges:/var/www/certbot:ro ports: - "${NGINX_BIND_ADDRESS:-127.0.0.1}:${NGINX_HTTP_PORT:-8080}:80" - "${NGINX_BIND_ADDRESS:-127.0.0.1}:${NGINX_HTTPS_PORT:-8443}:443" depends_on: backend: condition: service_healthy frontend: condition: service_healthy healthcheck: test: [ "CMD-SHELL", "wget -q -O /dev/null http://127.0.0.1/nginx-health || exit 1", ] interval: 10s timeout: 5s retries: 10 start_period: 10s security_opt: - no-new-privileges:true certbot: image: certbot/certbot:v5.6.0 restart: unless-stopped init: true read_only: true environment: LETSENCRYPT_DOMAIN: ${LETSENCRYPT_DOMAIN:-azionelab.org} LETSENCRYPT_EMAIL: "${LETSENCRYPT_EMAIL:-}" LETSENCRYPT_STAGING: ${LETSENCRYPT_STAGING:-1} LETSENCRYPT_RENEW_INTERVAL_SECONDS: ${LETSENCRYPT_RENEW_INTERVAL_SECONDS:-43200} LETSENCRYPT_RETRY_SECONDS: ${LETSENCRYPT_RETRY_SECONDS:-300} entrypoint: ["/bin/sh", "/opt/certbot/renew.sh"] volumes: - ./certbot/renew.sh:/opt/certbot/renew.sh:ro - letsencrypt_data:/etc/letsencrypt - certbot_challenges:/var/www/certbot tmpfs: - /tmp - /var/lib/letsencrypt - /var/log/letsencrypt depends_on: proxy: condition: service_healthy deploy: replicas: ${LETSENCRYPT_ENABLED:-0} security_opt: - no-new-privileges:true volumes: postgres_data: media_data: letsencrypt_data: certbot_challenges: