Self-host Kindgi
You run four containers on one Docker network:
- the runtime: Kindgi's server, with its API, agents and flows;
- Postgres;
- your pack's service: your tools' code;
- a registry the runtime reads your pack's image from.
You then deploy a pack to it, and run a flow end to end. Everything here runs on one machine with Docker Desktop. On a server the pieces are the same; step 2 says what changes.
Before you start
Section titled “Before you start”-
Docker, and Node 22 or later.
-
A pack. This page uses the sample:
Terminal window npx @kindgi/cli init acme-pack --template=samplecd acme-packpnpm install -
A license key. Outside development mode, the runtime needs one: a free non-production key covers staging and CI, and a production key comes with a commercial license. To get one: contact@kindgi.com. See Licensing.
kindgi devneeds none.
1. Pull the runtime image
Section titled “1. Pull the runtime image”docker login quay.iodocker pull quay.io/kindgi/runtime:0.1.02. Start Postgres and a registry
Section titled “2. Start Postgres and a registry”docker network create kindgi
export DB_PASSWORD="$(openssl rand -hex 24)"docker run -d --name kindgi-db --network kindgi \ -e POSTGRES_USER=kindgi -e POSTGRES_PASSWORD="$DB_PASSWORD" -e POSTGRES_DB=kindgi \ -v kindgi-db:/var/lib/postgresql/data \ pgvector/pgvector:pg16
docker run -d --name kindgi-registry -p 127.0.0.1:5050:5000 registry:2The runtime verifies your pack's image by reading it from a registry. On one machine with Docker Desktop, the name registry.localhost reaches the local registry from two places:
- Docker: it treats
*.localhostas loopback, so it pushes there over plain HTTP; - the runtime's container:
--add-host(step 5) maps the name to your machine.
On Linux or a server, use your own registry instead, and give the runtime its credentials (KINDGI_IMAGE_REGISTRY_HOST, _USERNAME, _PASSWORD).
3. Build, sign and push your pack
Section titled “3. Build, sign and push your pack”Pick a tenant id. The runtime serves this tenant, and the pack's signature names it:
uuidgen | tr 'A-Z' 'a-z'Create a signing key:
pnpm exec kindgi key create acme-selfhost --env selfhostAdd an environment for this deployment to kindgi.config.ts:
environments: { selfhost: { endpoint: 'http://localhost:4000', registry: 'registry.localhost:5050', tenantId: '<your tenant id>', signingKey: '~/.kindgi/keys/acme-selfhost.pem', signerKeyId: 'acme-selfhost', },},Build the image with your own Docker, push it, and sign it:
pnpm exec kindgi build --local --push --env selfhost ✓ Pushed registry.localhost:5050/acme-pack@sha256:11c12784… ✓ /app/index.json in the image matches the local index byte for byte ✓ Ed25519 signature over (imageDigest, artifactVersion, indexHash, tenantId, publishedAt) Deploy envelope written to …/acme-pack/.kindgi/build/deploy-envelope.jsonThe image is for linux/amd64 by default. On Apple silicon it runs under emulation; --platform picks another.
4. Run your pack's service
Section titled “4. Run your pack's service”Your tools' code runs in the pack's own container. The runtime calls it with a token both sides share:
echo "KINDGI_PACK_SERVICE_TOKEN=$(openssl rand -base64 32)" > pack.env
docker run -d --name kindgi-pack --network kindgi --env-file pack.env \ registry.localhost:5050/acme-pack@sha256:<the digest kindgi build printed>Its log says it's listening:
{"kind":"listening","port":8080,"packId":"acme-pack","artifactVersion":"20261003.1"}5. Configure and start the runtime
Section titled “5. Configure and start the runtime”Make an API token. Your CLI and apps send it as their bearer:
printf 'kgi_bt_%s\n' "$(openssl rand -hex 32)"Put the runtime's settings in kindgi.env:
KINDGI_DATABASE_URL=postgres://kindgi:<DB_PASSWORD>@kindgi-db:5432/kindgiKINDGI_TENANT_ID=<your tenant id>KINDGI_API_TOKEN=<the token>KINDGI_ENV=productionKINDGI_PACK_SERVICE_URL=http://kindgi-pack:8080KINDGI_PACK_SERVICE_TOKEN=<the same token as in pack.env>KINDGI_IMAGE_REGISTRY_INSECURE_HOSTS=registry.localhost:5050KINDGI_LICENSE_KEY=<your license key>Every setting is in the environment variable reference. Two are worth knowing now:
KINDGI_ENVnames the environment your tools' secrets resolve in.KINDGI_TENANT_HOST_ACCESSisn't set here, so it'sdeployed, the default outside development. It refuses an MCP endpoint that would run a command on the runtime's host (stdio). Run MCP servers over HTTP instead.localallows it; set that only on a machine where everyone with an API token may run commands.
Start the runtime:
docker run -d --name kindgi-server --network kindgi \ --add-host registry.localhost:host-gateway \ -p 127.0.0.1:4000:4000 --env-file kindgi.env \ quay.io/kindgi/runtime:0.1.06. Check it
Section titled “6. Check it”curl -s http://localhost:4000/health{"ok":true}Its log names what it's running with:
docker logs kindgi-serverKindgi API server listening on http://localhost:4000 Tenant: 8f34192d-53bb-4fc2-bfb8-9094157b2404 Token: kgi_bt_…abb1 (provided) … Deployments: on (signed images, /v1/deployments) License: Docs example · non-production · until 2026-11-02 Env: production (tool secrets resolve in it) Tenant host access: deployed (stdio MCP endpoints refused; KINDGI_TENANT_HOST_ACCESS) Pack service: http://kindgi-pack:8080 — acme-pack (artifact 20261003.1), protocol 2, 3 tools, 1 checkWithout KINDGI_LICENSE_KEY, the runtime doesn't start. It exits with code 2 and says:
KINDGI_LICENSE_KEY is not set. Outside development mode the Kindgi runtime needs a license key: a production key comes with a commercial license, and a free non-production key covers staging and CI. To get one: contact@kindgi.com. Local development needs none: `kindgi dev` runs the runtime with KINDGI_DEV=true.A key within 30 days of expiry adds a warning under the license line.
7. Trust your key and deploy
Section titled “7. Trust your key and deploy”The runtime deploys only images signed by a key its tenant trusts. Trust yours with its 32 raw bytes, base64:
export KINDGI_API_TOKEN=<the token from kindgi.env>PUB="$(pnpm exec kindgi key export acme-selfhost --format=raw-hex | xxd -r -p | base64)"
curl -s -X POST http://localhost:4000/v1/signing-keys \ -H "authorization: Bearer $KINDGI_API_TOKEN" -H 'content-type: application/json' \ -d "{\"keyId\":\"acme-selfhost\",\"publicKey\":\"$PUB\"}"Then deploy:
pnpm exec kindgi deploy --env selfhost --token "$KINDGI_API_TOKEN" "status": 201, "outcome": "created", … "primitives": { "tools": 3, "guardrails": 1, "agents": 1, "flows": 1 },The runtime checked the signature, read the pack's index from the image, and registered its tools, agents and flows.
8. Add a model, and run a flow
Section titled “8. Add a model, and run a flow”The sample's agent needs a model that can call tools. Any OpenAI-compatible endpoint whose model supports tool calling works. This example uses Ollama on the same machine (after ollama pull llama3.1), which needs no key. The runtime reaches it at host.docker.internal: Docker Desktop provides that name, and on Linux, add --add-host host.docker.internal:host-gateway to the runtime's docker run. Save it as ollama.json:
{ "adapter_id": "@kindgi/adapter-model-openai-compat", "adapter_config": { "baseURL": "http://host.docker.internal:11434/v1" }, "metadata": { "id": "ollama-local", "region": "unspecified", "models": [{ "name": "llama3.1", "contextWindow": 131072, "features": ["tool-use"], "cost": { "promptUsdPer1kTokens": 0, "completionUsdPer1kTokens": 0 } }] }}pnpm exec kindgi providers register --spec=@ollama.json --url http://localhost:4000 --token "$KINDGI_API_TOKEN"pnpm exec kindgi runs start --flow=acme-pack.echo-flow --input='{"name":"Ada"}' --url http://localhost:4000 --token "$KINDGI_API_TOKEN" "status": "completed", … "output": { "reply": "…Hello, Ada!…", "greeting": "Hello, Ada!" }The flow's tool step ran in your pack's container, and its agent step answered with the model. A first call can outlast the agent's time budget while the model loads; run it again.
Clean up
Section titled “Clean up”docker rm -f kindgi-server kindgi-pack kindgi-registry kindgi-dbdocker volume rm kindgi-dbdocker network rm kindgiOn Google Cloud Run
Section titled “On Google Cloud Run”Coming soon.