services: web: image: git.vicolab.de/schalli/tessera-ctl/web:latest restart: unless-stopped ports: - "3000:3000" environment: HOSTNAME: "0.0.0.0" # Read by the browser, not by the web container, so it must be an address # that resolves outside Docker. http://api:3001 only exists on the compose # network and leaves the UI unable to reach the API. NEXT_PUBLIC_API_URL: ${APP_URL:-http://localhost:3001} API_INTERNAL_URL: "http://api:3001" JWT_SECRET: ${JWT_SECRET} networks: - frontend-net - backend-net depends_on: api: condition: service_healthy api: image: git.vicolab.de/schalli/tessera-ctl/api:latest restart: unless-stopped ports: - "3001:3001" networks: - backend-net - data-net depends_on: db: condition: service_healthy environment: DATABASE_URL: ${DATABASE_URL} JWT_SECRET: ${JWT_SECRET} TESSERA_ADMIN_USER: ${TESSERA_ADMIN_USER:-admin} TESSERA_ADMIN_EMAIL: ${TESSERA_ADMIN_EMAIL} TESSERA_ADMIN_PASSWORD: ${TESSERA_ADMIN_PASSWORD} TESSERA_FORCE_CHANGE: ${TESSERA_FORCE_CHANGE:-true} TESSERA_SMTP_HOST: ${TESSERA_SMTP_HOST:-} TESSERA_SMTP_PORT: ${TESSERA_SMTP_PORT:-587} TESSERA_SMTP_SECURE: ${TESSERA_SMTP_SECURE:-false} TESSERA_SMTP_USER: ${TESSERA_SMTP_USER:-} TESSERA_SMTP_PASSWORD: ${TESSERA_SMTP_PASSWORD:-} TESSERA_SMTP_FROM: ${TESSERA_SMTP_FROM:-Tessera } TESSERA_APP_URL: ${APP_URL:-http://localhost:3000} # Unset used to resolve to an empty value and only fail later, inside the # API, with a stack trace. Fail at compose level with a usable message # instead. Generate with: openssl rand -hex 32 # # CALENDAR_ENCRYPTION_KEY is the previous name and is still accepted, so # an existing .env keeps working; the API logs a deprecation warning when # it falls back to it. TESSERA_ENCRYPTION_KEY: "${TESSERA_ENCRYPTION_KEY:-${CALENDAR_ENCRYPTION_KEY:?set TESSERA_ENCRYPTION_KEY in .env, generate one with openssl rand -hex 32}}" CALENDAR_ENCRYPTION_KEY: "${CALENDAR_ENCRYPTION_KEY:-}" healthcheck: test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:3001/health"] interval: 10s timeout: 5s retries: 3 start_period: 20s db: image: postgres:16-alpine restart: unless-stopped networks: - data-net volumes: - pgdata:/var/lib/postgresql/data environment: POSTGRES_USER: tessera POSTGRES_PASSWORD: ${DB_PASSWORD} POSTGRES_DB: tessera healthcheck: test: ["CMD-SHELL", "pg_isready -U tessera"] interval: 5s timeout: 3s retries: 5 networks: frontend-net: driver: bridge backend-net: driver: bridge data-net: driver: bridge internal: true volumes: pgdata: