ghcr.io/withcoral/coral and
ghcr.io/withcoral/coral-ui images on Kubernetes with authentication enabled
and TLS terminated at the Ingress. It assumes you have read
Self-host Coral with Docker, which explains
the listeners, auth modes, and image behavior that this guide builds on.
TLS termination is mandatory: every Coral listener is cleartext, and with
[auth] enabled bearer tokens would otherwise cross the wire unprotected. A
Kubernetes deployment is remote by definition, so this guide does not use
CORAL_UI_AUTH_MODE=disabled; if you need a login-less lab setup, use the
local Docker Compose topology
instead.
The example uses the same three hostnames as the Compose guide:
coral.example.com (Coral UI), auth.coral.example.com (OAuth issuer), and
mcp.coral.example.com (MCP).
Prerequisites
- A cluster with an Ingress controller (the manifests use ingress-nginx annotations) and cert-manager or another way to provision TLS certificates that chain to system roots.
- DNS records for the three hostnames pointing at the Ingress.
- An OIDC client registered with your identity provider, with redirect URI
https://auth.coral.example.com/auth/oidc/callback. - A default StorageClass for the Coral state volume.
During login, the Coral server fetches
https://coral.example.com/.well-known/oauth-client and Coral UI fetches the issuer’s discovery and token endpoints. From inside the cluster both coral.example.com and auth.coral.example.com must resolve to a public address (Coral rejects client-metadata hosts that resolve to private or loopback addresses), and the certificates must chain to system CA roots — Let’s Encrypt works, a private CA does not.Namespace and secrets
Coral server
The server is stateful — SQLite, workspace and source state, and credential material all live on one volume — so it runs as a single replica with theRecreate strategy. This stays true with a
Postgres backend, which moves the
database but not the workspace files.
The seed config below is applied on the first boot only; an existing
config.toml on the volume is never rewritten. See
changing the configuration.
Save the following as coral.yaml, replacing hostnames and the
[auth.provider] issuer and client ID with yours:
65532 with group 0; fsGroup: 65532 makes the
volume writable for it. The probes use the gRPC health service directly:
service "" is liveness (always serving while the process runs) and
coral.readiness reports whether the engine has resolved its catalog.
Coral UI
Coral UI is stateless — sessions live in encrypted cookies — so you can raisereplicas beyond the example’s single replica as needed; no sticky sessions
are required. It talks native gRPC to the coral Service inside the cluster;
CORAL_UI_ALLOW_INSECURE_CORAL_ENDPOINT=1 is the explicit opt-in for that
cleartext in-cluster leg.
Save as coral-ui.yaml, replacing the two https:// hostnames with yours:
Ingress
One Ingress terminates TLS for all three hostnames. MCP Streamable HTTP holds long-lived streaming responses, so response buffering is disabled and the read timeout raised. Save asingress.yaml, replacing the hostnames and the
cert-manager.io/cluster-issuer annotation with your issuer’s name:
GET https://auth.coral.example.com/.well-known/oauth-authorization-server
serves as an external check if you monitor each hostname.
Deploy and verify
https://coral.example.com/readyz returns ready, https://mcp.coral.example.com/mcp
returns 401 with a WWW-Authenticate challenge, and opening
https://coral.example.com signs you in through your identity provider —
without Coral’s approval page, since Coral UI’s client ID is listed in
trusted_clients.
Operations
Administration
Day-to-day administration happens in the Coral UI. For the rest, run the CLI inside the pod — it shares the server’s state safely:Changing the configuration later
The seed config applies only on first boot. To change configuration afterwards, edit the file on the volume and restart:docker compose cp, kubectl cp writes through tar running inside
the container as the container user, so file ownership stays correct.
Updating the ConfigMap alone does nothing once config.toml exists.
Backup and upgrades
Back up thecoral-data PersistentVolumeClaim; it holds everything the server
owns. For upgrades, pin matching image versions for coral and coral-ui
(release tags of the two images move together) and roll the Deployments: