services: db: image: postgres:16-alpine environment: POSTGRES_DB: ${POSTGRES_DB:-mobilityops} POSTGRES_USER: ${POSTGRES_USER:-mobilityops} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-mobilityops} healthcheck: test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-mobilityops} -d ${POSTGRES_DB:-mobilityops}"] interval: 5s timeout: 3s retries: 20 volumes: - mobilityops-db:/var/lib/postgresql/data networks: [mobilityops] api: build: context: . dockerfile: backend/Dockerfile environment: MOBILITYOPS_ENV: ${MOBILITYOPS_ENV:-development} MOBILITYOPS_DEMO_MODE: ${MOBILITYOPS_DEMO_MODE:-true} DATABASE_URL: ${DATABASE_URL:-postgresql+psycopg://mobilityops:mobilityops@db:5432/mobilityops} TZ: ${TZ:-Europe/Brussels} APP_SECRET: ${APP_SECRET:-replace-in-production} CORS_ALLOW_ORIGINS: ${MOBILITYOPS_PUBLIC_URL:-http://localhost:1228} KNOWLEDGE_PROVIDER: ${KNOWLEDGE_PROVIDER:-demo} RAGCORE_BASE_URL: ${RAGCORE_BASE_URL:-http://ragcore-api:8000} RAGCORE_TENANT: ${RAGCORE_TENANT:-northstar-mobility-demo} RAGCORE_WORKSPACE: ${RAGCORE_WORKSPACE:-mobilityops} RAGCORE_COLLECTION: ${RAGCORE_COLLECTION:-internal-procedures} RAGCORE_API_TOKEN: ${RAGCORE_API_TOKEN:-} RAGCORE_SPACE_ID: ${RAGCORE_SPACE_ID:-} N8N_WEBHOOK_URL: ${N8N_WEBHOOK_URL:-http://n8n:5678/webhook/mobilityops-return} N8N_WEBHOOK_TRIGGER_TOKEN: ${MOBILITYOPS_WEBHOOK_TRIGGER_TOKEN:-replace-me-n8n-webhook-trigger-token} N8N_CALLBACK_TOKEN: ${MOBILITYOPS_CALLBACK_TOKEN:-replace-me-n8n-callback-token} MCP_HUB_SERVICE_TOKEN: ${MCP_HUB_SERVICE_TOKEN:-replace-me-mcp-hub-token} MCP_HUB_REGISTRATION_ENABLED: ${MCP_HUB_REGISTRATION_ENABLED:-false} MCP_HUB_BASE_URL: ${MCP_HUB_BASE_URL:-} DEMO_ORGANIZATION_NAME: ${DEMO_ORGANIZATION_NAME:-Northstar Mobility} DEMO_TIMEZONE: ${DEMO_TIMEZONE:-Europe/Brussels} DEMO_ALLOW_RESET: ${DEMO_ALLOW_RESET:-true} MOBILITYOPS_PUBLIC_URL: ${MOBILITYOPS_PUBLIC_URL:-http://localhost:1228} SESSION_COOKIE_SECURE: ${SESSION_COOKIE_SECURE:-false} OIDC_ENABLED: ${OIDC_ENABLED:-false} OIDC_PROVIDER_NAME: ${OIDC_PROVIDER_NAME:-Organisatieaccount} OIDC_ISSUER_URL: ${OIDC_ISSUER_URL:-} OIDC_CLIENT_ID: ${OIDC_CLIENT_ID:-} OIDC_CLIENT_SECRET: ${OIDC_CLIENT_SECRET:-} OIDC_REDIRECT_URI: ${OIDC_REDIRECT_URI:-} OIDC_ALLOWED_EMAIL_DOMAINS: ${OIDC_ALLOWED_EMAIL_DOMAINS:-} OIDC_AUTO_PROVISION: ${OIDC_AUTO_PROVISION:-true} OIDC_DEFAULT_ROLE: ${OIDC_DEFAULT_ROLE:-rental_employee} LOG_LEVEL: ${LOG_LEVEL:-INFO} METRICS_BEARER_TOKEN: ${METRICS_BEARER_TOKEN:-} PRIVACY_MINIMUM_BOOKING_RETENTION_DAYS: ${PRIVACY_MINIMUM_BOOKING_RETENTION_DAYS:-30} PRIVACY_AUDIT_RETENTION_DAYS: ${PRIVACY_AUDIT_RETENTION_DAYS:-2555} PRIVACY_AUDIT_EXPORT_MAX_ROWS: ${PRIVACY_AUDIT_EXPORT_MAX_ROWS:-10000} logging: driver: json-file options: max-size: "10m" max-file: "5" ports: - "8128:8000" depends_on: db: condition: service_healthy healthcheck: test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8000/health/ready')"] interval: 10s timeout: 5s retries: 10 networks: [mobilityops] web: build: ./frontend environment: VITE_API_BASE_URL: ${MOBILITYOPS_API_URL:-http://localhost:8128} ports: - "1228:80" depends_on: api: condition: service_healthy networks: [mobilityops] n8n: # Explicit fallback only; production reuses the server's existing central n8n. image: n8nio/n8n:2.33.7 environment: TZ: ${TZ:-Europe/Brussels} GENERIC_TIMEZONE: ${TZ:-Europe/Brussels} N8N_ENCRYPTION_KEY: ${N8N_ENCRYPTION_KEY:-replace-me} N8N_BASIC_AUTH_ACTIVE: ${N8N_BASIC_AUTH_ACTIVE:-true} N8N_BASIC_AUTH_USER: ${N8N_BASIC_AUTH_USER:-admin} N8N_BASIC_AUTH_PASSWORD: ${N8N_BASIC_AUTH_PASSWORD:-change-me} N8N_SECURE_COOKIE: "false" N8N_BLOCK_ENV_ACCESS_IN_NODE: "false" MOBILITYOPS_CALLBACK_TOKEN: ${MOBILITYOPS_CALLBACK_TOKEN:-replace-me-n8n-callback-token} ports: - "5678:5678" volumes: - mobilityops-n8n:/home/node/.n8n - ./n8n:/imports:ro networks: [mobilityops] networks: mobilityops: volumes: mobilityops-db: mobilityops-n8n: