Every release pushes an immutable version tag (for example
0.13.0), and the
latest and major.minor tags of both images move together, so matching tags
are always compatible. Pin exact versions in production.
This guide covers two topologies: a loopback-only stack for trying the server
deployment on one machine, and a production stack behind a TLS-terminating
reverse proxy with authentication enabled.
How the pieces fit
The Coral server owns all state and serves up to three listeners, all cleartext, all configured inconfig.toml (see
Configuration):
- Native gRPC (
[server].bind_addr, port14555in the image’s default config) — the data plane Coral UI talks to. - MCP Streamable HTTP (
[server.mcp_http], opt-in) — serves/mcpfor MCP clients, plus health-check endpoints. - OAuth authorization server (
[auth].http_bind_addr, only with[auth]) — serves login, token, and discovery endpoints.
config.toml from the
CORAL_SEED_CONFIG environment variable if set, otherwise with a starter
config that binds gRPC to 0.0.0.0:14555. The seed applies once: an
existing config.toml on the volume is never rewritten, so later changes to
CORAL_SEED_CONFIG have no effect (see
Changing the configuration).
Local deployment
The local stack runs without authentication: the Coral server has no[auth]
configured and Coral UI runs with CORAL_UI_AUTH_MODE=disabled, talking gRPC
to Coral over the Compose network. Everything is published only on loopback.
You need Docker Engine with the Compose plugin.
Create compose.yaml:
http://localhost:3000. Verify readiness with:
allow_unauthenticated_non_loopback (see
Configuration). Point any MCP client that
supports the transport at:
127.0.0.1 only, and
why Coral prints an exposure warning at startup. It accepts requests addressed
to localhost, 127.0.0.1, ::1, or the bind IP; to reach it from a sibling
container by service name instead, add the name to
[server.mcp_http].allowed_hosts.
Remote deployment with TLS
The production stack adds authentication and a TLS-terminating reverse proxy (Caddy here, which provisions Let’s Encrypt certificates automatically). TLS termination in front of every listener is mandatory for remote deployments: Coral serves only cleartext, and with[auth] enabled bearer tokens would
otherwise cross the wire unprotected.
The example uses one domain per surface:
Prerequisites
- A host with Docker, ports 80 and 443 reachable from the internet, and DNS records for the three hostnames pointing at it.
- An OIDC client registered with your identity provider (Google, Okta,
Microsoft Entra, Keycloak, and others all work). Set its redirect URI to
https://auth.coral.example.com/auth/oidc/callbackand note the client ID and secret. Who can sign in to Coral is decided entirely by this provider — see Authentication.
During login, the Coral server itself fetches
https://coral.example.com/.well-known/oauth-client and Coral UI fetches the issuer’s discovery and token endpoints. Both coral.example.com and auth.coral.example.com must therefore resolve to a public address from inside the containers, and certificates must chain to system roots (Let’s Encrypt is fine, a private CA is not). Split-horizon DNS that answers with private addresses breaks login: Coral rejects client-metadata hosts that resolve to private or loopback addresses.Secrets
Generate the two secrets and put them in a.env file next to the compose
file, together with your IdP client secret (replace your-idp-client-secret):
CORAL_SESSION_SIGNING_KEY signs Coral’s access tokens; losing it invalidates
every session, so treat it like any other production key material.
Compose file
Createcompose.yaml, replacing the example.com hostnames and the
[auth.provider] issuer and client ID with yours:
coral:14555 inside the Compose
network. That leg is cleartext on an operator-controlled network, which Coral
UI refuses by default; CORAL_UI_ALLOW_INSECURE_CORAL_ENDPOINT=1 is the
explicit opt-in for exactly this wiring. To avoid the opt-in, terminate TLS in
front of the gRPC listener too and point CORAL_ENDPOINT at an https://
URL.
And the Caddyfile:
Start and verify
{"coral":"reachable","status":"ok"} once Coral UI can
reach a ready Coral server. The second returns 401 Unauthorized with a
WWW-Authenticate header pointing at the OAuth resource metadata — that is an
MCP client’s entry point into the login flow, so a 401 here means MCP is
wired correctly.
Open https://coral.example.com in a browser. You are redirected to your
identity provider’s login and land back in Coral UI signed in. The
trusted_clients entry in the seed config is Coral UI’s own client ID, so its
sign-ins skip Coral’s approval page; drop that line to have every sign-in
pause on the approval page instead (see
Configuration).
Connect an MCP client
Point any MCP client that supports Streamable HTTP and OAuth at:trusted_clients — before
your identity provider’s sign-in. No token needs to be copied by hand.
Administering a deployed instance
Most administration happens in the Coral UI: installing, editing, importing, and removing sources, creating workspaces, browsing the schema and traces, and toggling runtime features. Thecoral CLI inside the container operates on the same state as the server
(concurrent access is safe — state access is serialized through a file lock),
which covers the remaining operations:
Changing the configuration later
CORAL_SEED_CONFIG only seeds the first boot. Afterwards, edit the config
on the volume and restart:
exec keeps the file owned by the container user; copying it
back in with docker compose cp would leave it root-owned and unreadable to
the server.
Persistence and backup
Everything the server owns lives on thecoral-data volume: config.toml,
the SQLite database, workspace and source state, and credential material. Back
up that volume. To move state to Postgres instead of SQLite, see
Database — workspace and source files
remain on the volume either way, so the Coral server stays a
single-instance deployment.
Upgrades
To upgrade, editcompose.yaml to the new version, keeping the coral and
coral-ui tags identical (release tags of the two images move together),
then:
Health endpoints
Environment variable reference
coral container
The server reads its networking, auth, and telemetry configuration from
config.toml, not environment variables. See
Configuration. The container-level variables:
Variables named by
config.toml keys — [database].url_env,
[auth.provider].client_secret_env, [auth.session].signing_key_env — must
also be provided when configured.