networks: # Service-to-service traffic (postgres <-> backend <-> frontend) and outbound egress. internal: driver: bridge traefik: external: true name: ${TRAEFIK_NETWORK:-traefik} services: postgres: image: pgvector/pgvector:pg17 restart: unless-stopped environment: POSTGRES_DB: multica POSTGRES_USER: ${POSTGRES_USER:-multica} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?POSTGRES_PASSWORD must be set} networks: - internal volumes: - ${POSTGRES_DATA_DIR:-./data/postgres}:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-multica} -d multica"] interval: 5s timeout: 5s retries: 5 # Official image. Its entrypoint runs `migrate up` before starting the server. # Version is pinned to match the multica CLI on the host; see README "Versioning". backend: image: ghcr.io/multica-ai/multica-backend:v0.6.5 restart: unless-stopped environment: APP_ENV: production PORT: "8080" DATABASE_URL: postgres://${POSTGRES_USER:-multica}:${POSTGRES_PASSWORD}@postgres:5432/multica?sslmode=disable JWT_SECRET: ${JWT_SECRET:?JWT_SECRET must be set - openssl rand -hex 32} FRONTEND_ORIGIN: ${FRONTEND_ORIGIN:?FRONTEND_ORIGIN must be set} CORS_ALLOWED_ORIGINS: ${FRONTEND_ORIGIN} MULTICA_APP_URL: ${FRONTEND_ORIGIN} MULTICA_PUBLIC_URL: ${MULTICA_PUBLIC_URL:-} GOOGLE_REDIRECT_URI: ${FRONTEND_ORIGIN}/auth/callback GOOGLE_CLIENT_ID: ${GOOGLE_CLIENT_ID:-} GOOGLE_CLIENT_SECRET: ${GOOGLE_CLIENT_SECRET:-} RESEND_API_KEY: ${RESEND_API_KEY:-} RESEND_FROM_EMAIL: ${RESEND_FROM_EMAIL:-noreply@multica.ai} SMTP_HOST: ${SMTP_HOST:-} SMTP_PORT: ${SMTP_PORT:-25} SMTP_USERNAME: ${SMTP_USERNAME:-} SMTP_PASSWORD: ${SMTP_PASSWORD:-} SMTP_FROM_EMAIL: ${SMTP_FROM_EMAIL:-} SMTP_TLS: ${SMTP_TLS:-} ALLOW_SIGNUP: ${ALLOW_SIGNUP:-true} ALLOWED_EMAILS: ${ALLOWED_EMAILS:-} ALLOWED_EMAIL_DOMAINS: ${ALLOWED_EMAIL_DOMAINS:-} DISABLE_WORKSPACE_CREATION: ${DISABLE_WORKSPACE_CREATION:-false} MULTICA_VCS_INTEGRATION_ENABLED: "true" MULTICA_VCS_SECRET_KEY: ${MULTICA_VCS_SECRET_KEY:-} DO_NOT_TRACK: ${DO_NOT_TRACK:-} networks: - internal - traefik volumes: - ${UPLOADS_DATA_DIR:-./data/uploads}:/app/data/uploads # Loopback only, for the multica agent daemon on this host. ports: - "127.0.0.1:${BACKEND_HOST_PORT:-8080}:8080" depends_on: postgres: condition: service_healthy labels: - "traefik.enable=true" - "traefik.docker.network=${TRAEFIK_NETWORK:-traefik}" - "traefik.http.services.multica-backend.loadbalancer.server.port=8080" # On the traefik network only so traefik can reach /ws; everything else # goes through the frontend. # Next.js rewrites proxy /api, /auth, /uploads to the backend, but not # websockets, so /ws on the main host must go straight to the backend. - "traefik.http.routers.multica-ws.rule=Host(`${FRONTEND_HOST}`) && (Path(`/ws`) || PathPrefix(`/ws/`))" - "traefik.http.routers.multica-ws.entrypoints=websecure" - "traefik.http.routers.multica-ws.tls.certresolver=letsencrypt" - "traefik.http.routers.multica-ws.service=multica-backend" # Official image. Proxies API calls to REMOTE_API_URL at runtime. frontend: image: ghcr.io/multica-ai/multica-web:v0.6.5 restart: unless-stopped environment: HOSTNAME: "0.0.0.0" REMOTE_API_URL: http://backend:8080 networks: - internal - traefik depends_on: - backend labels: - "traefik.enable=true" - "traefik.docker.network=${TRAEFIK_NETWORK:-traefik}" - "traefik.http.routers.multica-web.rule=Host(`${FRONTEND_HOST}`)" - "traefik.http.routers.multica-web.entrypoints=websecure" - "traefik.http.routers.multica-web.tls.certresolver=letsencrypt" - "traefik.http.services.multica-web.loadbalancer.server.port=3000"