# The frontend in production. `make prod-frontend`. # # docker compose -p agenticos-frontend --env-file backend/.env \ # -f docker-compose-prod.frontend.yml pull # docker compose -p agenticos-frontend --env-file backend/.env \ # -f docker-compose-prod.frontend.yml up -d # # `-p` is not optional and not tidiness. Compose names a project after the # directory, so without it this shares one with `docker-compose-prod.yml` - and # reports that stack's five containers as **orphans** of this one, suggesting the # `--remove-orphans` flag that would stop the API, the database, Redis and the # worker. # # Start `docker-compose-prod.yml` first: this joins the `edge` network it creates # and declares it `external: true`, so bringing the frontend up without a backend # fails immediately instead of serving a site whose every request 502s. # # It joins `edge` rather than `data`: it talks to the app and to the browser, and # has no business reaching Postgres. # # `PUBLIC_*` are what the console tells the *browser* to call, so they must be # the public URLs, not container names. Getting one wrong is the classic "works # on the server, broken in the browser" bug: SSR keeps working over the compose # network while every client-side call goes to the wrong host. They are read # when the container starts (#1544), so the image is the published one and a # change is a restart rather than a rebuild. # Every service's log, capped. Docker's `json-file` driver keeps a container's # output until the container is removed, and nothing else on the host rotates it: # prod held 570 MB of logs and growing when this was added (#2228). Five files of # 20 MB is about a day of a busy API and every line `docker compose logs` needs. x-logging: &logging driver: json-file options: max-size: 20m max-file: "5" services: frontend: logging: *logging image: ghcr.io/vstorm-co/agenticos-frontend:${AGENTICOS_VERSION:-latest} environment: - NODE_ENV=production - BACKEND_URL=http://app:8000 - PUBLIC_API_URL=${PUBLIC_API_URL:?PUBLIC_API_URL is required, e.g. https://api.example.com} - PUBLIC_WS_URL=${PUBLIC_WS_URL:?PUBLIC_WS_URL is required, e.g. wss://api.example.com} - PUBLIC_SITE_URL=${PUBLIC_SITE_URL:?PUBLIC_SITE_URL is required, e.g. https://app.example.com} # Where artifact pages are framed from, when not the API origin above. - ARTIFACT_ORIGIN=${ARTIFACT_ORIGIN:-} - CHAT_MAX_UPLOAD_SIZE_MB=${CHAT_MAX_UPLOAD_SIZE_MB:-10} - OAUTH_PROVIDERS=${OAUTH_PROVIDERS:-google} - OIDC_DISPLAY_NAME=${OIDC_DISPLAY_NAME:-} - OIDC_ICON=${OIDC_ICON:-} - LDAP_DISPLAY_NAME=${LDAP_DISPLAY_NAME:-} - KERBEROS_DISPLAY_NAME=${KERBEROS_DISPLAY_NAME:-} # `instrumentation.ts` registers an OpenTelemetry SDK on every boot, and # `@vercel/otel` off Vercel takes its exporter from these two - so without # them the frontend's server-side spans are built and dropped in-process. # Passed through empty by default rather than omitted: an operator who # points them at Logfire (the same project the backend writes to) then gets # traces without editing a compose file. `frontend/README.md` has the pair. - OTEL_EXPORTER_OTLP_ENDPOINT=${OTEL_EXPORTER_OTLP_ENDPOINT:-} - OTEL_EXPORTER_OTLP_HEADERS=${OTEL_EXPORTER_OTLP_HEADERS:-} ports: # Loopback by default. The reverse proxy in front of this stack runs on # the host and reaches it here; nothing else on the network does. That # matters beyond the usual: the BFF forwards the caller's # `X-Forwarded-For` to the backend's auth rate limiter, and where # `RATE_LIMIT_TRUST_FORWARDED_FOR` is on, anything that can reach this # port directly chooses the address its login attempts are counted # against. `BIND_HOST=0.0.0.0` for a proxy that is genuinely elsewhere. - "${BIND_HOST:-127.0.0.1}:3000:3000" networks: edge: # `nginx/nginx.conf` proxies to `frontend:3000`. aliases: - frontend restart: unless-stopped healthcheck: # The oven/bun image has no curl/wget, so probe with bun's built-in fetch. test: ["CMD", "bun", "-e", "fetch('http://localhost:3000/api/health').then((r) => process.exit(r.ok ? 0 : 1), () => process.exit(1))"] interval: 30s timeout: 10s start_period: 40s retries: 3 deploy: resources: limits: cpus: "1" memory: 512M networks: edge: name: ${AGENTICOS_EDGE_NETWORK:-agenticos_edge} external: true