Docker Compose
The services, the images, and running Kai against your own Postgres, Redis and S3.
infra/docker-compose.yml runs everything Kai needs. Start it with:
docker compose -f infra/docker-compose.yml up -d --buildThe Quickstart walks through .env.
Services
| Service | Image | |
|---|---|---|
kai | built from infra/Dockerfile (target kai) | The server: the API and the web app on one port (KAI_PORT, default 3000). |
migrate | the same kai image | Runs node server.mjs migrate once before kai starts: creates the database roles, applies migrations, exits. |
library | built from infra/Dockerfile (target library) | The MCP catalog, its icons and model data. Published on LIBRARY_PUBLIC_PORT (default 4100), because browsers load the icons from it. |
postgres | postgres:17-alpine | Data. Not published. |
redis | redis:7-alpine | Run event streams, rate limits, short-lived caches. Not published. |
s3, s3-init | chrislusf/seaweedfs | Object storage for attachments and large tool outputs, and a one-shot job that creates the bucket. Published on S3_PUBLIC_PORT (default 9010), because browsers upload and download directly. |
Compose points Kai at the other services by their service names
(POSTGRES_HOST=postgres, REDIS_URL=redis://redis:6379, S3_ENDPOINT=http://s3:8333,
KAI_LIBRARY_URL=http://library:4100), overriding .env.
The images
| Image | Pulled (compressed) | On disk |
|---|---|---|
kai | about 53 MB | about 134 MB |
library | about 31 MB | about 76 MB |
Both run on Alpine with its own Node 22 build. The kai image is one bundled
server file, the built web app, the SQL migrations and sharp (for resizing
uploaded images). It has full ICU data so dates and numbers format correctly
in every locale. Neither image runs as root.
Build them on their own with:
docker build -f infra/Dockerfile --target kai -t kai .
docker build -f infra/Dockerfile --target library -t kai-library .Using your own Postgres, Redis and object storage
Kai only needs to reach them. Drop the postgres, redis and s3 services
from the Compose file (or run the kai image any other way) and set:
- Postgres (Kai is developed and tested on 17):
POSTGRES_HOST,POSTGRES_PORT,POSTGRES_DATABASE, and the superuser (POSTGRES_USER,POSTGRES_PASSWORD) thatmigrateuses to create Kai's three roles and their passwords (POSTGRES_APP_*,POSTGRES_AUTH_*,POSTGRES_SERVICE_*). The server itself connects only as those roles. - Redis (developed on 7; Kai uses streams):
REDIS_URL. - S3-compatible storage (AWS S3, Cloudflare R2, MinIO, SeaweedFS…):
S3_ENDPOINT,S3_REGION,S3_BUCKET,S3_ACCESS_KEY_ID,S3_SECRET_ACCESS_KEY,S3_FORCE_PATH_STYLE, andS3_PUBLIC_ENDPOINTwhen browsers reach it at a different address. Browsers upload with presigned URLs, so the bucket's CORS rules must allowPUTandGETfromKAI_APP_URL.
Run the migrations before starting a new version:
docker run --rm --env-file .env kai node server.mjs migrateThe library service
The library (apps/library) serves:
- the MCP catalog that Browse library shows, and its icons;
- model capabilities (context window, output limit, reasoning efforts, web and tool search support), built from public model datasheets.
It fetches the datasheets at start and every LIBRARY_MODELS_REFRESH_HOURS
(default 6), keeps the last good copy if a refresh fails, and works offline
from the copy in its image. It holds no secrets and needs no database.
| Variable | Default | |
|---|---|---|
PORT | 4100 | |
LIBRARY_PUBLIC_URL | http://localhost:<PORT> | The address browsers use, for icon URLs. Compose sets it from KAI_LIBRARY_PUBLIC_URL. |
LIBRARY_MODELS_URL | Bifrost's model datasheet | Where model data comes from. |
LIBRARY_MODEL_PARAMS_URL | Bifrost's model parameters sheet | |
LIBRARY_MODELS_REFRESH_HOURS | 6 |
Without a library (KAI_LIBRARY_URL unset), the MCP library is hidden and
models get per-provider defaults.
Upgrading
git pull
docker compose -f infra/docker-compose.yml up -d --buildmigrate runs again before the new kai starts. Runs in progress on the old
container get 20 seconds to finish; anything still running is marked
interrupted, and people can regenerate.