# One origin in front of the whole stack. # - Plain http on :80 for IP-only testing (the default when OC8_DOMAIN is empty). # - For automatic HTTPS, set OC8_DOMAIN in your .env (e.g. oc8.example.com) -- # docker-compose.yml already publishes 443 and passes it into this container's # environment. Caddy then provisions a Let's Encrypt cert for it on startup. # DNS must already point at this host before you start the stack, or issuance # fails. # The `:80` fallback lives in docker-compose.yml's `${OC8_DOMAIN:-:80}`, not # here: Caddy's own `{$VAR:default}` only substitutes the default when the # variable is entirely UNSET, but docker-compose's `environment:` always sets # it (to "" when .env leaves OC8_DOMAIN empty) -- Caddy's default would never # fire, leaving an empty address and a config Caddy refuses to parse. {$OC8_DOMAIN} { # Backend: the API and the root-level liveness endpoint. handle /api/* { reverse_proxy backend:8099 } handle /health { reverse_proxy backend:8099 } # The two agent-facing gateways. Agent containers do NOT come through here -- # they reach backend:8099 directly on the internal `agents` network -- but # without these routes the stack cannot be exercised from outside itself, and # every operator ends up hand-rolling a socat container to test a run. Both # demand an agent token (`kind=agent`, scope `run:`): an operator token is # refused, so publishing them widens the surface no further than /api does. handle /llm/* { reverse_proxy backend:8099 } handle /mcp* { reverse_proxy backend:8099 } # Everything else is the SSR frontend. handle { reverse_proxy frontend:3000 } }