Quickstart with Docker Compose
Run a released Kelta stack on your machine in two commands and sign in to the console.
The repository ships a Docker Compose stack with every service and backing store. It is the fastest way to evaluate Kelta or develop against it. For production, see Kubernetes deployment. Check the prerequisites below first, then run the steps in this section — they are README.md’s Quickstart, included verbatim at build time.
Start the stack#
Start a released Kelta stack, open the UI, and create your first collection. Nothing is
built: Compose pulls the version pinned by KELTA_VERSION in .env from ghcr.io/kelta-io,
so you need only Docker Compose v2.24+, make and git — no JDK, Maven or Node. Host ports
5173 (UI) and 8080 (gateway) must be free — lsof -i :5173 -i :8080 should print nothing.
They cannot be remapped today: the released UI bundle calls http://localhost:8080 and the OAuth
redirect is http://localhost:5173/auth/callback.
git clone https://github.com/kelta-io/kelta.git
cd kelta
make setup # first time only: copies .env.example → .env, generates dev signing keys
docker compose up -d --wait # pulls + starts postgres, redis, nats, cerbos, mailpit, auth, worker, gateway, ui
To build this checkout from source instead, run make up-jvm — see
From source.
- Open http://localhost:5173/default/ and sign in as
admin@kelta.localwith the first-boot password kelta-auth prints once in its log, then choose your own:docker compose logs --no-color kelta-auth | sed -n 's/.*Password: *\([A-Za-z0-9]*\).*/\1/p' | head -n 1. SetKELTA_BOOTSTRAP_ADMIN_PASSWORDin.envbefore the first start to pick it instead. The/default/path is the tenant — the barehttp://localhost:5173shows a Tenant Required page. - Go to Setup → Data Model → Collections, click Create Collection, fill in the wizard (Basics → Fields → Authorization → Review), then click Create Collection on the Review step.
That's it — a running, runtime-configurable Kelta instance with your first collection. See Local Development below for ports, debugging, and the full service list.
Prerequisites#
- Docker and Docker Compose v2.24+ (Docker Desktop or the Docker Engine plugin).
makeandgit.- Free host ports 5173 and 8080 (UI and gateway) —
lsof -i :5173 -i :8080should print nothing. They cannot be remapped today: the released UI bundle callshttp://localhost:8080and the OAuth redirect ishttp://localhost:5173/auth/callback. - Memory. A default Docker Desktop allocation is enough — the commands above pull prebuilt JVM images. Building the GraalVM native images production runs needs roughly 24 GB allocated to Docker; see Native images if you want them.
What the commands do#
make setup writes an .env from .env.example and generates the secrets that have no defaults: an RSA JWK set
for JWT signing (JWK_SET) and an AES-256 key for envelope encryption (KELTA_ENCRYPTION_KEY). Services that need a
signing key fail to start without one rather than falling back to a shared value.
docker compose up -d --wait pulls ghcr.io/kelta-io/{kelta-auth,kelta-worker,kelta-gateway,kelta-ui} at the
version KELTA_VERSION names in .env, starts them with the backing stores, and returns once every service is
healthy. Nothing is compiled. The quickstart CI job pulls the same images and times this path on every relevant
change. To run another release, change KELTA_VERSION and run the command again.
Build from source#
To run your own checkout rather than a release, use the make targets: each layers docker-compose.build.yml on
docker-compose.yml and builds the Kelta services from source.
make up-jvm # JVM images: a default Docker allocation, ~2–3 min per service
make seed # waits for a healthy stack, prints login info
Native images#
make up builds GraalVM native images instead — what production runs. The three Java services build
concurrently and each sizes its heap to most of the memory Docker reports, so it needs roughly 24 GB allocated to
Docker; with less, it fails with cannot allocate memory, which is native-image running out of heap rather than a
code error. Use it when you need production parity, for example when debugging native reflection configuration.
make up (native) | make up-jvm | |
|---|---|---|
| Docker memory needed | ~24 GB | default allocation |
| Build time | ~10 min per service | ~2–3 min per service |
| Startup | ~50 ms | ~20–40 s |
Ports, container names and environment are identical in both modes; switching recreates the containers but keeps the volumes.
Sign in#
Open http://localhost:5173/default/ and sign in as the platform administrator. The /default/ path is the
tenant; the bare http://localhost:5173 shows a Tenant Required page instead:
| Field | Value |
|---|---|
admin@kelta.local | |
| Password | the first-boot password (below) — you are asked to change it on first sign-in |
| Tenant | default |
Kelta does not ship a default administrator password. On its first boot, kelta-auth replaces the seeded one and
prints the new password once in a warning banner headed Kelta platform admin initial password:
docker compose logs --no-color kelta-auth | sed -n 's/.*Password: *\([A-Za-z0-9]*\).*/\1/p' | head -n 1
(The services log JSON, so the banner arrives as one line; run docker compose logs kelta-auth to see all of
it.) The banner is printed only once — on later restarts the account is already changed and nothing is logged.
To choose the initial password yourself instead, set KELTA_BOOTSTRAP_ADMIN_PASSWORD in .env (or your shell)
before the first start. It is applied once, never logged, and you are still asked to change it at first
sign-in. Setting it after the first boot has no effect.
You land in the admin console. Continue with your first app, or install a ready-made CRM or inspections app with one command: Start from a template.
Service ports#
| Service | Port | Notes |
|---|---|---|
| kelta-ui | 5173 | Admin console and end-user app |
| kelta-gateway | 8080 | API entry point — the URL you give the CLI and SDK |
| kelta-auth | 8081 | OIDC provider |
| kelta-worker | 8083 | Not called directly; reachable for debugging |
| kelta-ai | 8084 | --profile ai |
| Cerbos | 3592 (HTTP) / 3593 (gRPC) | Authorization engine |
| PostgreSQL | 5432 | |
| Redis | 6379 | |
| NATS | 4222 | |
| Mailpit | 8025 | Captures every email the stack sends (--profile tools) |
| pgAdmin | 8092 | --profile tools |
| Redis Commander | 8091 | --profile tools |
Optional profiles#
These build from source (kelta-ai is not a released image):
make up-ai # + kelta-ai — needs ANTHROPIC_API_KEY in .env
make up-full # + ai + tools (pgAdmin, Redis Commander, Mailpit)
make up-telehealth # + LiveKit SFU for video visits (dev keys built in)
The compose stack does not include kelta-mcp, the hosted MCP server. To use MCP tools against a local stack,
run the CLI’s local bridge instead: kelta mcp serve --source local (see Local MCP bridge).
Everyday commands#
make logs SVC=kelta-gateway # tail one service
make rebuild SVC=kelta-worker # rebuild + restart one service (rebuild-jvm for JVM mode)
make ps # container status
make down # stop everything, keep data
make reset-jvm # stop, wipe volumes, start clean (JVM images)
make help # every target
Point the CLI at your local stack#
Install the CLI (instructions), then log in. On localhost the auth server
cannot be derived from the API host, so pass it explicitly:
kelta auth login --url http://localhost:8080 --tenant default --auth-url http://localhost:8081
kelta collections list
Or skip the browser entirely with a personal access token created under Profile → API tokens in the app:
kelta auth login --url http://localhost:8080 --tenant default --token klt_...
Troubleshooting#
- Gateway answers 404 for
/api/...right after start. The gateway builds its route table from the worker’s collection registry on startup; wait fordocker compose up -d --waitto return (ormake seedto report healthy) — both wait on the gateway’s readiness check, not liveness. - Emails never arrive. In the compose stack all mail goes to Mailpit on http://localhost:8025 — start it with
--profile tools/make up-full. cannot allocate memoryduring build. See Native images: usemake up-jvmor raise Docker’s memory limit.