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.12 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.22. 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”If your app's code needs a generate step in the image (Prisma's client, for example), set that up first: What the pack's image needs.
In a Python pack, run each pnpm exec kindgi on this page as
npx --yes @kindgi/cli@0.1, and lock its dependencies first (uv lock, or
poetry lock): the image installs them from the lockfile.
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', },},In a Python pack, the same keys go in pyproject.toml:
[tool.kindgi.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:faca44e8… ✓ /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.jsonA Python pack's build also says where its dependencies come from:
✓ 22 pack file(s) in the image (the pack root, minus caches, virtualenvs and secrets); dependencies from uv.lockThe 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.envchmod 600 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>It holds the API token and the license key, so keep it to yourself:
chmod 600 kindgi.env.
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.26. Check it
Section titled “6. Check it”curl -s http://localhost:4000/ready{"ok":true,"database":"ok"}/ready answers once the runtime is up and its database answers (/health checks only the process; see Operate).
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 ⚠ The license key expires in 29 days (2026-11-02). Renew it: contact@kindgi.com. 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 the warning under the license line, as this example key does.
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:
export KINDGI_API_TOKEN=<the token from kindgi.env>pnpm exec kindgi key trust acme-selfhost --url http://localhost:4000 --token "$KINDGI_API_TOKEN" ✓ Trusted acme-selfhost (sha256:7bfbd97be075d796eb24f80d)The fingerprint is the one kindgi key create printed.
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": "…", "greeting": "Hello, Ada!" }The flow's tool step ran in your pack's container, and its agent step answered with the model; what the reply says depends on 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 kindgiOperate a self-hosted runtime: health and logs, backups, upgrades, and rotating its tokens and keys.
On Google Cloud Run
Section titled “On Google Cloud Run”Coming soon.