Service Kong - thekaveh/atlas GitHub Wiki
Kong is Atlas's API gateway. It routes *.localhost requests to services, using a configuration that ./start.sh generates from the current SOURCE settings. It also applies authentication, CORS and timeouts per route.
Kong runs DB-less (KONG_DATABASE: "off") and loads one declarative file. It is the entry point for most browser and API traffic to the stack.
bootstrapper/utils/kong_config_generator.py builds the Kong configuration on every ./start.sh:
- Route generation: each enabled service gets its routes.
-
Disabled services: a service with
*_SOURCE=disabledgets no route. - Health check (advisory): the generator probes localhost-sourced services and warns if they are down. It creates the route either way.
volumes/api/kong-dynamic.yml is a generated runtime file, not a checked-in file. It is in .gitignore and reflects the resolved SOURCE state at the last ./start.sh. A direct docker compose up from a clean checkout fails because the bind-mount source does not exist. Always start through ./start.sh, which writes the file before it runs compose.
Validate the generator contract without ./start.sh. The checker copies .env.example to a temporary directory, runs kong_config_generator against it, and checks the output. It does not read your local volumes/api/kong-dynamic.yml, which depends on your .env.
uv run --project bootstrapper python scripts/check-kong-routes.pypython3 scripts/check-kong-routes.py also works if PyYAML is installed in your system Python. Otherwise it prints FAIL import: PyYAML is required to parse Kong config and exits with status 2. The uv form uses the project's pinned dependencies.
-
/on barelocalhostโ Atlas service directory and health dashboard -
/auth/v1/โ Supabase Auth service -
/rest/v1/โ Supabase API (PostgREST) -
/graphql/v1โ Supabase GraphQL -
/realtime/v1/โ Supabase Realtime -
/storage/v1/โ Supabase Storage (key-auth)./storage/v1/object/public/,/storage/v1/object/sign/and/storage/v1/object/upload/sign/carry CORS only. Browsers and outside services fetch public-bucket and signed URLs without anapikey; Storage enforces the bucket's public flag or the signed token. -
/pg/โ Supabase Meta service -
supabase-studio.localhostโ Supabase Studio dashboard
Allowed Host names. The path routes answer only on these Hosts:
-
localhost,127.0.0.1,kong-api-gateway(the in-network name),<PROJECT_NAME>-kong-api-gateway(the container name) andhost.docker.internal. - Each bare hostname or IPv4 address in
KONG_SUPABASE_EXTRA_HOSTS. Use it for a tunnel hostname, the LAN address whenHOST_BIND_IPexposes Kong, or the host of a customAPI_EXTERNAL_URL/SITE_URL.
Any other Host gets 404, so a public hostname does not reach Supabase by accident. A Host header with a port still matches. Matching is case-sensitive, so list hosts in lowercase. IPv6 literals cannot be listed.
Consumers. The key-auth services also carry an acl that admits only the anon (SUPABASE_ANON_KEY) and admin (SUPABASE_SERVICE_KEY) groups, as upstream Supabase does. Another Kong key, such as BACKEND_KONG_API_KEY, gets 403 there.
Kong publishes one <alias>.localhost host per enabled service. Ports and Routes ยง2 lists every host, its SOURCE condition and its auth gate. The generated ยง15.1 table lists every service Kong proxies; volumes/api/kong-dynamic.yml is the exact route set for your .env.
Example: curl http://lightrag.localhost:${KONG_HTTP_PORT}/health
Localhost sources. Each *-localhost source still gets a Kong route that proxies through host.docker.internal to the host.
- Kong's compose entry sets
extra_hosts: ["host.docker.internal:${HOST_GATEWAY_IP}"], so this works on Linux Docker. Docker Desktop resolves the name itself. - For a non-default host port, set
<SVC>_LOCALHOST_PORT. The in-container consumers (runtime_sc.<svc>.localhost.environment) and the Kong generator both read it and usehttp://host.docker.internal:${<SVC>_LOCALHOST_PORT}.
# Generated based on COMFYUI_SOURCE (simplified)
if source == 'managed-localhost-mps':
service['url'] = localhost_url('COMFYUI_MPS_LOCALHOST_PORT', '8188')
elif source == 'localhost':
service['url'] = localhost_url('COMFYUI_LOCALHOST_PORT', '8000')
else: # container-cpu / container-gpu
service['url'] = 'http://comfyui:18188/'
# No route created if source == 'disabled'Kong 3.x has no global proxy-timeout setting, and its per-service default is 60 s. The generator therefore writes read_timeout and write_timeout on every service in kong-dynamic.yml:
| Service | Timeout |
|---|---|
| Default (including n8n, so long webhooks are not cut off) | 300 s |
docling-api |
DOCLING_INFERENCE_TIMEOUT_SECONDS + 30 s (930 s by default) |
asset-baker |
ASSET_BAKER_TIMEOUT_SECONDS + 30 s (630 s by default) |
Backend api.localhost
|
3,630 s, or the Docling value + 30 s if larger. Backend requests wait on Docling or on a ComfyUI job of up to 3,600 s. |
| Backend plugin services | the plugin's own values; the Backend value for any field the plugin does not set |
litellm-gateway, ollama-api
|
630 s, for slow non-streaming completions |
Kong retries only on connection errors (KONG_NGINX_PROXY_PROXY_NEXT_UPSTREAM=error). A request that times out on connect, send or read fails after one wait and is not re-sent.
check_localhost_service() opens a TCP connection to each localhost-sourced service with a 2-second timeout. If it fails, the generator prints a warning and still creates the route. The route returns errors until the host service starts.
Kong applies one of three schemes per route:
-
API key (
key-auth): Supabase API services and protected Backend routes. Backend routes explicitly setkey_names: [apikey]andkey_in_query: truefor browser WebSockets. -
Basic (
basic-auth): protected dashboards. - Pass-through: services that handle their own auth.
On a Basic-auth route Kong reads the dashboard credential from Authorization or Proxy-Authorization. Some clients need Authorization for the service's own token: Crawl4AI's Bearer, Label Studio's Token, and Langfuse's public-API Basic pk:sk. They send the dashboard credential as Proxy-Authorization: Basic โฆ alongside it. Trino's route strips the credential before forwarding (hide_credentials), because Trino rejects any password over plain HTTP.
Blocked paths. Two routes answer 403 at the gateway and never reach the upstream. Use the internal network for these paths:
-
s3.minio.localhost: the MinIO metrics paths (/minio/v2/metrics,/minio/metrics/v3,/minio/prometheus/metrics). -
prometheus.localhost:/-/quitand/-/reload.
Kong runs with KONG_PORT_MAPS=${KONG_HTTP_PORT}:8000,${KONG_HTTPS_PORT}:8443. X-Forwarded-Port therefore carries the published port, so upstreams that build absolute URLs from it (Trino redirects and nextUri) point back at Kong.
Kong sets X-Forwarded-Host itself, without a port, and overwrites any value a plugin adds. The n8n route therefore also adds an RFC 7239 Forwarded: host=n8n.localhost:<port>;proto=http header, which n8n's editor origin check reads first. That header names the HTTP port, so n8n's editor live connection works through Kong over HTTP only.
A client inside the Docker network that calls kong-api-gateway:8000 directly also sees the published port in X-Forwarded-Port. Supabase Storage builds S3 signatures and resumable-upload (TUS) URLs from it. S3 or TUS calls through Kong from another container therefore need STORAGE_PUBLIC_URL. Atlas's own containers use only the Storage REST API.
Every service gets a CORS plugin. A bare {'name': 'cors'} answers Access-Control-Allow-Origin: *, which would let any website the operator visits read responses from the no-login services. The generator (_with_local_cors) scopes each plugin:
-
Allowed origins: any
*.localhost,localhostor127.0.0.1page on any port, plus the exact origins inKONG_CORS_EXTRA_ORIGINS(for a LAN or tunnel front-end). Other origins get noAccess-Control-Allow-Origin. -
KONG_CORS_EXTRA_ORIGINSformat: entries are normalised to the browser's form: lowercase, no trailing slash, no default port. A wildcard or path is ignored with a warning. - Not an authentication boundary: simple cross-site requests, such as plain POSTs, still reach the upstream.
- Kong never adds
Access-Control-Allow-Credentials, but an upstream's own header passes through for allowed origins. -
[::1]origins cannot match, because Kong drops the brackets. Uselocalhostor127.0.0.1. - Upstream CORS settings, such as
BACKEND_CORS_ORIGINS, apply only inside what Kong allows.
Some services include rate limiting for protection:
# SearxNG example
{
'name': 'rate-limiting',
'config': {
'minute': 60,
'hour': 1000,
'policy': 'local'
}
}Kong supports WebSocket connections for real-time services:
{
'name': 'realtime-v1-ws',
'url': 'http://supabase-realtime:4000/socket',
'protocol': 'ws',
# ...
}-
Startup:
./start.shrunsgenerate_kong_configurationafter service configuration and dependency checks, and before LiteLLM configuration. -
Environment parsing: the generator reads SOURCE values from the parsed
.env. - Health checks: the generator probes localhost-sourced services and warns if they are down (ยง4.3).
- Route generation: only enabled services get routes.
-
File writing: the configuration is written to
volumes/api/kong-dynamic.yml. -
Kong startup: Kong loads the generated file from
/home/kong/kong.yml.
Kong loads only the plugins named in KONG_PLUGINS (services/kong/compose.yml). DB-less Kong rejects the whole declarative file if it names any other plugin, which takes down every route. A generator change that emits a new plugin must add it to KONG_PLUGINS. tests/test_kong_route_and_auth_invariants.py checks every emitted plugin against the list.
# Check what configuration was generated
cat volumes/api/kong-dynamic.yml
# View Kong logs
docker logs ${PROJECT_NAME}-kong-api-gateway -f
# Test Kong routing end-to-end (proxies SearXNG's /healthz through Kong;
# the bare-localhost root serves the generated Atlas dashboard)
curl -H 'Host: search.localhost' http://localhost:${KONG_HTTP_PORT}/healthz# Validate the generated declarative config inside the running gateway
docker exec ${PROJECT_NAME}-kong-api-gateway kong config parse /home/kong/kong.yml
# List hosts and paths Kong will route
grep -nE '^\s+(hosts|paths):' -A2 volumes/api/kong-dynamic.yml
# Test specific routes
curl -H "Host: comfyui.localhost" http://localhost:${KONG_HTTP_PORT}/
curl -H "Host: n8n.localhost" http://localhost:${KONG_HTTP_PORT}/
curl -H "Host: jupyter.localhost" http://localhost:${KONG_HTTP_PORT}/
curl -H "Host: openclaw.localhost" http://localhost:${KONG_HTTP_PORT}/
curl -H "Host: hermes.localhost" http://localhost:${KONG_HTTP_PORT}/
curl -H "Host: api.localhost" http://localhost:${KONG_HTTP_PORT}/health
# If BACKEND_KONG_AUTH=key-auth:
curl -H "Host: api.localhost" -H "apikey: ${BACKEND_KONG_API_KEY}" http://localhost:${KONG_HTTP_PORT}/health
curl -H "Host: litellm.localhost" http://localhost:${KONG_HTTP_PORT}/ui/
curl -H "Host: minio.localhost" http://localhost:${KONG_HTTP_PORT}/
curl -H "Host: spark.localhost" http://localhost:${KONG_HTTP_PORT}/
curl -H "Host: spark-history.localhost" http://localhost:${KONG_HTTP_PORT}/
curl -u "${DASHBOARD_USERNAME}:${DASHBOARD_PASSWORD}" -H "Host: trino.localhost" http://localhost:${KONG_HTTP_PORT}/
curl -u "${DASHBOARD_USERNAME}:${DASHBOARD_PASSWORD}" -H "Host: redpanda.localhost" http://localhost:${KONG_HTTP_PORT}/
curl -H "Host: airflow.localhost" http://localhost:${KONG_HTTP_PORT}/
# Airflow REST API (same alias). 3.x is JWT-only โ exchange password
# for a token via /auth/token, then call /api/v2/ with Bearer auth:
TOKEN=$(curl -fsS -X POST -H "Host: airflow.localhost" \
-H 'Content-Type: application/json' \
-d "{\"username\":\"admin\",\"password\":\"${AIRFLOW_ADMIN_PASSWORD}\"}" \
http://localhost:${KONG_HTTP_PORT}/auth/token | jq -r .access_token)
curl -H "Host: airflow.localhost" -H "Authorization: Bearer $TOKEN" \
http://localhost:${KONG_HTTP_PORT}/api/v2/dagsFor advanced Kong configuration needs, modify the KongConfigGenerator class in bootstrapper/utils/kong_config_generator.py.
Key methods:
-
generate_kong_config()- Main configuration generator -
check_localhost_service()- Health check implementation -
generate_*_service()- Service-specific route generators
ยง15 lists the services Kong proxies and the services that call Kong.
Support tier: experimental โ Capability contract declared; no cited cold-start, workflow, or upgrade qualification run yet (evidence at v0.1.0).
| Capability | Status | Verification | Notes |
|---|---|---|---|
| SOURCE-aware dynamic gateway routing | supported | tested | The bootstrapper generates DB-less Kong routes for enabled container and localhost sources, including service-specific upstream paths and health contracts. |
| Route authentication and traffic policy | partial | tested | Atlas applies Basic, key-auth, pass-through, rate-limit, and CORS policies per route, but it does not enforce one uniform identity policy across every upstream. |
| Direct Compose startup from a clean checkout | not-supported | documented | Kong requires the declarative configuration generated by the Atlas startup pipeline, so operators must use ./start.sh before Compose can launch the gateway. |
Route not found (404)
- Check if service SOURCE is enabled
- Verify service is running and healthy
- Check hosts file configuration
Connection refused
- For localhost routes, ensure service is running on specified port
- Check firewall settings for localhost services
- Verify Docker network connectivity
Authentication errors
- Check if service requires API key authentication
- Verify Supabase keys are properly generated
- Ensure proper headers are sent
# Check Kong gateway status
docker compose ps | grep kong
# View detailed Kong configuration
docker exec ${PROJECT_NAME}-kong-api-gateway cat /home/kong/kong.yml
# Check that the gateway is up (the image has no curl or wget)
docker exec ${PROJECT_NAME}-kong-api-gateway kong health| Service | Category | Status |
|---|---|---|
| grafana | infra | current |
| langfuse | infra | current |
| prometheus โ | infra | current |
| ray | infra | optional: RAY_SOURCE=ray-container-cpu or ray-container-gpu |
| minio | data | current |
| neo4j | data | current |
| redpanda | data | current |
| spark | data | current |
| supabase | data | current |
| trino | data | current |
| weaviate | data | current |
| litellm | llm | current |
| ollama | llm | current |
| tei-reranker | llm | current |
| asset-baker | media | current |
| asset-worker | media | current |
| comfyui | media | current |
| crawl4ai | media | current |
| doc-processor | media | current |
| searxng | media | current |
| stt-provider | media | current |
| tika | media | current |
| tts-provider | media | current |
| airflow | agents | current |
| celery | agents | current |
| hermes | agents | current |
| lightrag | agents | current |
| mcp-servers | agents | current |
| n8n | agents | current |
| openclaw | agents | current |
| trueforge | agents | optional: TRUEFORGE_SOURCE=container |
| backend โ | apps | current |
| jenkins | apps | current |
| jupyterhub | apps | current |
| label-studio | apps | current |
| llm-graph-builder | apps | current |
| local-deep-researcher | apps | current |
| mlflow | apps | current |
| open-webui | apps | current |
| verba | apps | current |
| Service | Category | Status |
|---|---|---|
| cloudflared | infra | optional: public hostnames configured in the Cloudflare dashboard |
| prometheus โ | infra | current |
| backend โ | apps | current |

Open the full-size diagram for a full-screen view.
-
kong โ multi2vec-clip โ Why: with CLIP's raw
/vectorsendpoint behind Kong, backend, n8n and jupyterhub could compute embeddings without a Weaviate query. This enables re-ranking and offline batch jobs. Mechanism: aliasclip.localhostโhttp://multi2vec-clip:8080/vectors, gated byMULTI2VEC_CLIP_SOURCE != disabled, CORS plugin only. Effort: small. Confidence: low.
- Keycloak โ Headline: self-hosted OIDC/OAuth2 provider. It would replace per-service basic-auth with one SSO layer behind Kong. Wires into: kong, jupyterhub, open-webui, n8n, minio, neo4j, openclaw, backend.
-
Per-consumer metrics โ Why pursue: the global
prometheusplugin runs withper_consumer: false, so metrics cannot be split by API consumer. Effort: small. -
opentelemetryplugin โ Why pursue: emit OTLP spans for every gateway hop so requests through Kong โ LiteLLM โ Ollama can be stitched into a single trace. Effort: small. -
jwtplugin (replacing per-route basic-auth) โ Why pursue: validate JWTs against the Supabase GoTrue keys in.env. This would secure jupyter, n8n, openclaw and hermes without a new identity service. Effort: medium. -
request-size-limitingplugin โ Why pursue: ComfyUI and Docling routes accept arbitrarily large multipart uploads; a 100 MB cap at the gateway prevents accidental host OOM. Effort: small. -
correlation-idplugin โ Why pursue: injectX-Request-IDon ingress so backend/litellm/hermes logs become joinable across the request path. Effort: small. -
ai-proxyplugin โ Why pursue: Kong's AI Gateway normalizes OpenAI/Anthropic/Ollama request shapes at the edge, worth evaluating as a comparison (not replacement) for LiteLLM's role. Effort: large. -
ai-prompt-guardplugin โ Why pursue: regex allow/deny on prompt content at the gateway gives a defense-in-depth layer before LiteLLM. Effort: medium. -
Health-check active probing โ Why pursue: swap the one-shot TCP probe at startup for Kong's
healthchecks.activeblock so localhost services auto-recover when they bounce. Effort: small. -
Admin API on a private host port โ Why pursue: Kong's admin API binds to container loopback (
127.0.0.1:8001). The image has nocurlto query it. Publishing read-only/statuson an internal host port would serve external health dashboards. Effort: small.