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.

  1. Open http://localhost:5173/default/ and sign in as admin@kelta.local with 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. Set KELTA_BOOTSTRAP_ADMIN_PASSWORD in .env before the first start to pick it instead. The /default/ path is the tenant — the bare http://localhost:5173 shows a Tenant Required page.
  2. 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).
  • make and git.
  • Free host ports 5173 and 8080 (UI and gateway) — 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.
  • 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 GBdefault 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:

FieldValue
Emailadmin@kelta.local
Passwordthe first-boot password (below) — you are asked to change it on first sign-in
Tenantdefault

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#

ServicePortNotes
kelta-ui5173Admin console and end-user app
kelta-gateway8080API entry point — the URL you give the CLI and SDK
kelta-auth8081OIDC provider
kelta-worker8083Not called directly; reachable for debugging
kelta-ai8084--profile ai
Cerbos3592 (HTTP) / 3593 (gRPC)Authorization engine
PostgreSQL5432
Redis6379
NATS4222
Mailpit8025Captures every email the stack sends (--profile tools)
pgAdmin8092--profile tools
Redis Commander8091--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 for docker compose up -d --wait to return (or make seed to 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 memory during build. See Native images: use make up-jvm or raise Docker’s memory limit.

Edit this page on GitHub