elmeg-demo/docker-compose.yml
2025-12-26 22:52:06 -08:00

193 lines
6.5 KiB
YAML

version: '3.8'
services:
backend:
build: ./backend
ports:
- "127.0.0.1:8020:8000"
volumes:
- ./backend:/app
- backend_data:/app/data
environment:
- DATABASE_URL=${DATABASE_URL:-postgresql://elmeg:elmeg_password@db:5432/elmeg}
- SECRET_KEY=${SECRET_KEY:-demo-secret-change-in-production}
# Postal SMTP (primary)
- SMTP_HOST=${SMTP_HOST}
- SMTP_PORT=${SMTP_PORT:-25}
- SMTP_USERNAME=${SMTP_USERNAME}
- SMTP_PASSWORD=${SMTP_PASSWORD}
- SMTP_USE_TLS=${SMTP_USE_TLS:-true}
# AWS SES (fallback)
- AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID}
- AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY}
- AWS_SES_REGION=${AWS_SES_REGION}
# Common
- EMAIL_FROM=${EMAIL_FROM:-noreply@elmeg.xyz}
- FRONTEND_URL=${FRONTEND_URL:-https://elmeg.xyz}
command: sh start.sh
depends_on:
- db
restart: unless-stopped
healthcheck:
test: [ "CMD", "python", "-c", "import httpx; httpx.get('http://localhost:8000/docs').raise_for_status()" ]
interval: 30s
timeout: 10s
retries: 3
networks:
- elmeg
- traefik-public
- postal-internal
labels:
- "traefik.enable=true"
- "traefik.http.routers.elmeg-backend.rule=(Host(`elmeg.runfoo.run`) || Host(`elmeg.xyz`)) && PathPrefix(`/api`)"
- "traefik.http.routers.elmeg-backend.entrypoints=websecure"
- "traefik.http.routers.elmeg-backend.tls.certresolver=letsencrypt"
- "traefik.http.routers.elmeg-backend.priority=100"
- "traefik.http.middlewares.elmeg-strip.stripprefix.prefixes=/api"
- "traefik.http.routers.elmeg-backend.middlewares=elmeg-strip"
- "traefik.http.routers.elmeg-backend.service=elmeg-backend-svc"
- "traefik.http.services.elmeg-backend-svc.loadbalancer.server.port=8000"
- "traefik.docker.network=traefik"
# Direct routes for docs (no strip)
- "traefik.http.routers.elmeg-backend-docs.rule=(Host(`elmeg.runfoo.run`) || Host(`elmeg.xyz`)) && PathPrefix(`/docs`, `/openapi.json`)"
- "traefik.http.routers.elmeg-backend-docs.entrypoints=websecure"
- "traefik.http.routers.elmeg-backend-docs.tls.certresolver=letsencrypt"
- "traefik.http.routers.elmeg-backend-docs.priority=100"
- "traefik.http.routers.elmeg-backend-docs.service=elmeg-backend-svc"
frontend:
build:
context: ./frontend
dockerfile: Dockerfile
ports:
- "127.0.0.1:3020:3000"
volumes:
- ./frontend:/app
- /app/node_modules
environment:
- NEXT_PUBLIC_API_URL=/api
- INTERNAL_API_URL=http://backend:8000
depends_on:
backend:
condition: service_healthy
restart: unless-stopped
networks:
- elmeg
- traefik-public
labels:
- "traefik.enable=true"
- "traefik.http.routers.elmeg-frontend.rule=(Host(`elmeg.runfoo.run`) || Host(`elmeg.xyz`)) && !PathPrefix(`/api`, `/docs`, `/openapi.json`)"
- "traefik.http.routers.elmeg-frontend.entrypoints=websecure"
- "traefik.http.routers.elmeg-frontend.tls.certresolver=letsencrypt"
- "traefik.http.routers.elmeg-frontend.priority=50"
- "traefik.http.services.elmeg-frontend.loadbalancer.server.port=3000"
- "traefik.docker.network=traefik"
# bugs.elmeg.xyz subdomain - redirects to /bugs
- "traefik.http.routers.elmeg-bugs.rule=Host(`bugs.elmeg.xyz`)"
- "traefik.http.routers.elmeg-bugs.entrypoints=websecure"
- "traefik.http.routers.elmeg-bugs.tls.certresolver=letsencrypt"
- "traefik.http.middlewares.bugs-redirect.redirectregex.regex=^https://bugs\\.elmeg\\.xyz/(.*)"
- "traefik.http.middlewares.bugs-redirect.redirectregex.replacement=https://elmeg.xyz/bugs/$${1}"
- "traefik.http.middlewares.bugs-redirect.redirectregex.permanent=true"
- "traefik.http.routers.elmeg-bugs.middlewares=bugs-redirect"
- "traefik.http.routers.elmeg-bugs.service=elmeg-frontend"
db:
image: postgres:15-alpine
environment:
- POSTGRES_USER=elmeg
- POSTGRES_PASSWORD=elmeg_password
- POSTGRES_DB=elmeg
volumes:
- postgres_data:/var/lib/postgresql/data
restart: unless-stopped
healthcheck:
test: [ "CMD-SHELL", "pg_isready -U elmeg -d elmeg" ]
interval: 10s
timeout: 5s
retries: 5
networks:
- elmeg
db-backup:
image: prodrigestivill/postgres-backup-local:15-alpine
restart: unless-stopped
volumes:
- ./backups:/backups
- postgres_data:/var/lib/postgresql/data:ro
environment:
- POSTGRES_HOST=db
- POSTGRES_DB=elmeg
- POSTGRES_USER=elmeg
- POSTGRES_PASSWORD=elmeg_password
- SCHEDULE=@daily
- BACKUP_KEEP_DAYS=7
- BACKUP_KEEP_WEEKS=4
- BACKUP_KEEP_MONTHS=6
- HEALTHCHECK_PORT=80
depends_on:
- db
networks:
- elmeg
# Weekly digest email scheduler
scheduler:
image: mcuadros/ofelia:latest
command: daemon --docker
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
labels:
# Run weekly digest every Sunday at 9am UTC
- "ofelia.job-exec.weekly-digest.schedule=0 9 * * 0"
- "ofelia.job-exec.weekly-digest.container=elmeg-demo-backend-1"
- "ofelia.job-exec.weekly-digest.command=python services/weekly_digest.py"
restart: unless-stopped
networks:
- elmeg
umami:
image: ghcr.io/umami-software/umami:postgresql-latest
restart: unless-stopped
environment:
- DATABASE_URL=postgresql://umami:umami_password@umami-db:5432/umami
- APP_SECRET=${UMAMI_SECRET:-highly-secret-key-change-this}
- TRACKER_SCRIPT_NAME=stats
depends_on:
- umami-db
networks:
- elmeg
- traefik-public
labels:
- "traefik.enable=true"
- "traefik.http.routers.elmeg-umami.rule=Host(`stats.elmeg.xyz`) || Host(`stats.elmeg.runfoo.run`)"
- "traefik.http.routers.elmeg-umami.entrypoints=websecure"
- "traefik.http.routers.elmeg-umami.tls.certresolver=letsencrypt"
- "traefik.http.services.elmeg-umami.loadbalancer.server.port=3000"
- "traefik.docker.network=${TRAEFIK_NETWORK:-traefik}"
umami-db:
image: postgres:15-alpine
environment:
- POSTGRES_USER=umami
- POSTGRES_PASSWORD=umami_password
- POSTGRES_DB=umami
volumes:
- umami_data:/var/lib/postgresql/data
restart: unless-stopped
networks:
- elmeg
volumes:
postgres_data:
backend_data:
umami_data:
networks:
elmeg:
traefik-public:
name: ${TRAEFIK_NETWORK:-traefik}
external: true
postal-internal:
name: postal_postal-internal
external: true