Calendars Compose Configuration¶
Calendars requires three services: backend, worker, and frontend. It also needs a CalDAV server (SabreDAV/PHP) for calendar storage.
All services join the messages_internal network to share PostgreSQL, Redis, and reach Keycloak.
compose.yml¶
name: calendars
services:
backend:
image: ghcr.io/suitenumerique/calendars-backend:main
restart: unless-stopped
volumes:
- ./translations.json:/data/translations.json:ro
env_file: .env
command: ["gunicorn", "calendars.wsgi:application", "--bind", "0.0.0.0:8000", "--workers", "2", "--timeout", "90"]
extra_hosts:
- "auth.messages.<domain>:host-gateway"
healthcheck:
test: ["CMD", "pgrep", "gunicorn"]
interval: 10s
timeout: 5s
retries: 20
start_period: 20s
labels:
- "traefik.enable=true"
- "traefik.http.routers.calendars-backend.rule=Host(`api.calendars.<domain>`)"
- "traefik.http.routers.calendars-backend.entrypoints=websecure"
- "traefik.http.routers.calendars-backend.tls=true"
- "traefik.http.routers.calendars-backend.tls.certresolver=letsencrypt"
- "traefik.http.services.calendars-backend.loadbalancer.server.port=8000"
networks:
proxy:
messages_internal:
messages_static: {ipv4_address: 172.29.0.30}
worker:
image: ghcr.io/suitenumerique/calendars-backend:main
restart: unless-stopped
command: ["python", "worker.py", "-v", "2"]
env_file: .env
extra_hosts:
- "auth.messages.<domain>:host-gateway"
depends_on: {backend: {condition: service_healthy}}
networks: [messages_internal]
caldav:
image: calendars-caldav:latest
restart: unless-stopped
command: >
sh -c "
/usr/local/bin/init-database.sh || true &&
apache2-foreground
"
environment:
PGHOST: postgresql
PGPORT: 5432
PGDATABASE: caldav
PGUSER: messages
PGPASSWORD: <db-password>
CALDAV_DB_SCHEMA: sabre
CALDAV_BASE_URI: /caldav/
CALDAV_INBOUND_API_KEY: <caldav-api-key>
CALDAV_OUTBOUND_API_KEY: <caldav-api-key>
CALDAV_INTERNAL_API_KEY: <caldav-api-key>
CALDAV_CALLBACK_BASE_URL: http://172.29.0.30:8000
networks:
messages_internal:
messages_static: {ipv4_address: 172.29.0.35}
frontend:
image: ghcr.io/suitenumerique/calendars-frontend:main
restart: unless-stopped
command: ["caddy", "run", "--config", "/etc/caddy/Caddyfile.prod", "--adapter", "caddyfile"]
env_file: .env
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile.prod:ro
depends_on: {backend: {condition: service_healthy}}
labels:
- "traefik.enable=true"
- "traefik.http.routers.calendars-frontend.rule=Host(`calendars.<domain>`)"
- "traefik.http.routers.calendars-frontend.entrypoints=websecure"
- "traefik.http.routers.calendars-frontend.tls=true"
- "traefik.http.routers.calendars-frontend.tls.certresolver=letsencrypt"
- "traefik.http.services.calendars-frontend.loadbalancer.server.port=8080"
networks:
proxy:
messages_internal:
messages_static: {ipv4_address: 172.29.0.31}
networks:
proxy: {external: true}
messages_internal: {external: true}
messages_static: {external: true}
Key Points¶
- Backend healthcheck uses
pgrep gunicorn— the Calendars backend doesn't have a/__heartbeat__/endpoint - CalDAV callback URL uses static IP
172.29.0.30:8000— avoids DNS conflicts withbackendhostname - Custom Caddyfile mounted at
/etc/caddy/Caddyfile.prod— adds API, CalDAV, and RSVP reverse proxy rules translations.jsonmounted from the host — required for email invitation generationextra_hostsresolves Keycloak domain to the Docker host gateway