# Environment variables

> Every KINDGI_* variable the Kindgi runtime and the pack service read.

Every `KINDGI_*` variable the Kindgi runtime (the server) and the pack service read. Generated from `@kindgi/env-schema`, Kindgi's own list, so it matches this version.

The `KINDGI_` prefix is reserved for Kindgi: a pack can't declare such a name in its `env`, and `kindgi dev` never resolves one as a secret, so an app's own variables stay its own.

## Core server config

### `KINDGI_API_PORT`

**Synopsis**

```sh signature
KINDGI_API_PORT=<value>
```

HTTP port the Kindgi API server listens on. The first that's set wins: the `--port` flag, `KINDGI_API_PORT`, the platform's `PORT` (Cloud Run, Render, Heroku and Fly set it and send traffic only there), the config file's `port`, then 4000.

**Values**

- **Read by:** the server
- **Required:** no
- **Default:** the platform's `PORT` when set, else `4000`

**Example**

```sh
KINDGI_API_PORT=4000
```

**See also**

- [Self-host Kindgi](https://docs.kindgi.com/v0.1.8/deploy/self-host.md)

### `KINDGI_API_HOST`

**Synopsis**

```sh signature
KINDGI_API_HOST=<value>
```

Address the API server binds. Default: all interfaces (what a container platform such as Cloud Run needs). `127.0.0.1` keeps it off the network; `kindgi dev` sets that on Linux, where the runtime container shares the host network.

**Values**

- **Read by:** the server
- **Required:** no
- **Default:** none: binds all interfaces

**Example**

```sh
KINDGI_API_HOST=127.0.0.1
```

**See also**

- [Self-host Kindgi](https://docs.kindgi.com/v0.1.8/deploy/self-host.md)
- [Operate a self-hosted runtime](https://docs.kindgi.com/v0.1.8/deploy/operate.md)

### `KINDGI_PUBLIC_URL`

**Synopsis**

```sh signature
KINDGI_PUBLIC_URL=<value>
```

The URL clients reach the API server at, when it isn't the address the server binds: behind a proxy or a load balancer, or in a container whose port is published on another one (`kindgi dev` sets it). The startup banner names it, with the docs and console links. An http(s) URL with no query or fragment. Default: the address the server binds.

**Values**

- **Read by:** the server
- **Required:** no
- **Default:** none: the address the server binds (shown as `http://localhost:<port>` when it binds all interfaces)

**Example**

```sh
KINDGI_PUBLIC_URL=https://kindgi.example.com
```

**See also**

- [Deploy on Google Cloud Run](https://docs.kindgi.com/v0.1.8/deploy/cloud-run.md)
- [Operate a self-hosted runtime](https://docs.kindgi.com/v0.1.8/deploy/operate.md)
- [Self-host Kindgi](https://docs.kindgi.com/v0.1.8/deploy/self-host.md)
- [Serve many tenants](https://docs.kindgi.com/v0.1.8/deploy/serve-many-tenants.md)

### `KINDGI_DATABASE_URL`

**Synopsis**

```sh signature
KINDGI_DATABASE_URL=<value>
```

Postgres connection string. Default `postgres://localhost:5432/kindgi` (dev only).

**Values**

- **Read by:** the server
- **Required:** no
- **Default:** `postgres://localhost:5432/kindgi`

**Example**

```sh
KINDGI_DATABASE_URL=postgres://user:pass@postgres.internal:5432/kindgi
```

**See also**

- [Self-host Kindgi](https://docs.kindgi.com/v0.1.8/deploy/self-host.md)

### `KINDGI_OPENFGA_API_URL`

**Synopsis**

```sh signature
KINDGI_OPENFGA_API_URL=<value>
```

OpenFGA HTTP API URL. Set → per-tenant FGA stores are bootstrapped and authorization is enforced; unset → routes mount without enforcement (dev only; production MUST set it).

**Values**

- **Read by:** the server
- **Required:** no
- **Default:** none: authorization is not enforced

**Example**

```sh
KINDGI_OPENFGA_API_URL=http://openfga.internal:8080
```

**See also**

- [Operate a self-hosted runtime](https://docs.kindgi.com/v0.1.8/deploy/operate.md)
- [Serve many tenants](https://docs.kindgi.com/v0.1.8/deploy/serve-many-tenants.md)

### `KINDGI_OPENFGA_PRESHARED_KEY`

**Synopsis**

```sh signature
KINDGI_OPENFGA_PRESHARED_KEY=<value>
```

Used from runtime 0.1.8; earlier runtimes ignore it. OpenFGA's preshared key, for an OpenFGA run with `--authn-method=preshared` (`OPENFGA_AUTHN_PRESHARED_KEYS`): the server sends it with every call, as `Authorization: Bearer <key>`, so nothing else on the network can read or write permissions. Needs `KINDGI_OPENFGA_API_URL`; `KINDGI_TENANCY=multi` starts only with both. Set it by reference to a secret, pinned to a version. Use a long random value (`openssl rand -hex 32`); the server drops surrounding whitespace and refuses spaces, commas or control characters inside (it travels in an HTTP header, and OpenFGA's list is comma-separated). To rotate it without a refusal: add the new key to OpenFGA's list, then point the server at it, then drop the old one, each step its own deploy.

**Values**

- **Read by:** the server
- **Required:** no
- **Default:** none: calls to OpenFGA carry no key

**Example**

```sh
# long random value without spaces or commas
KINDGI_OPENFGA_PRESHARED_KEY="$(openssl rand -hex 32)"
```

**See also**

- [Run with authorization](https://docs.kindgi.com/v0.1.8/deploy/authorization.md)
- [Deploy on Google Cloud Run](https://docs.kindgi.com/v0.1.8/deploy/cloud-run.md)
- [Serve many tenants](https://docs.kindgi.com/v0.1.8/deploy/serve-many-tenants.md)

### `KINDGI_OPENFGA_AUTH`

**Synopsis**

```sh signature
KINDGI_OPENFGA_AUTH=none|google-id-token
```

Used from runtime 0.1.8; earlier runtimes ignore it. What the server sends OpenFGA besides the preshared key. `none` (default). `google-id-token`: also a Google ID token for `KINDGI_OPENFGA_API_URL`, from the server's own identity, for an OpenFGA on Cloud Run behind IAM; the URL must be https. It goes in `X-Serverless-Authorization`, which Cloud Run checks in place of `Authorization` (and passes on without its signature), because `Authorization` carries the key.

**Values**

- **Read by:** the server
- **Required:** no
- **Values:** `none`, `google-id-token`
- **Default:** `none`

**Example**

```sh
KINDGI_OPENFGA_AUTH=none
```

**See also**

- [Deploy on Google Cloud Run](https://docs.kindgi.com/v0.1.8/deploy/cloud-run.md)

### `KINDGI_TENANT_ID`

**Synopsis**

```sh signature
KINDGI_TENANT_ID=<value>
```

Tenant UUID to seed / reuse. Default: fresh UUID printed at boot.

**Values**

- **Read by:** the server
- **Required:** no
- **Default:** none: a new tenant with a generated UUID is created at each start and printed in the startup banner

**Example**

```sh
KINDGI_TENANT_ID=00000000-0000-4000-8000-000000000001
```

**See also**

- [Security](https://docs.kindgi.com/v0.1.8/concepts/security.md)
- [Deploy on Google Cloud Run](https://docs.kindgi.com/v0.1.8/deploy/cloud-run.md)
- [Operate a self-hosted runtime](https://docs.kindgi.com/v0.1.8/deploy/operate.md)
- [Serve many tenants](https://docs.kindgi.com/v0.1.8/deploy/serve-many-tenants.md)

### `KINDGI_TENANCY`

**Synopsis**

```sh signature
KINDGI_TENANCY=single|multi
```

Used from runtime 0.1.8; earlier runtimes ignore it. How many tenants the runtime serves. `single` (default): one, `KINDGI_TENANT_ID`'s, as before. `multi`: many, each created through `POST /v1/system/tenants`. A request's tenant comes from its key or session, so `KINDGI_TENANT_ID` is refused, and `KINDGI_API_TOKEN` acts as the operator (`kindgi:system`) in no tenant. The first `multi` boot keeps the existing tenant, with its data, keys and people, as the first of many. `multi` starts only with all of these: `KINDGI_TENANT_HOST_ACCESS=isolated`, `KINDGI_PACK_ROUTING=per-deployment` with `KINDGI_PACK_ENDPOINT_HOSTS`, `KINDGI_OPENFGA_API_URL` with `KINDGI_OPENFGA_PRESHARED_KEY`, `KINDGI_PRODUCTION_GATE=on` and `KINDGI_USAGE_METERING=on`. A start that lacks any is refused, naming each.

**Values**

- **Read by:** the server
- **Required:** no
- **Values:** `single`, `multi`
- **Default:** `single`: the runtime serves one tenant, `KINDGI_TENANT_ID`'s

**Example**

```sh
KINDGI_TENANCY=single
```

**See also**

- [Deploy on Google Cloud Run](https://docs.kindgi.com/v0.1.8/deploy/cloud-run.md)

### `KINDGI_API_TOKEN`

**Synopsis**

```sh signature
KINDGI_API_TOKEN=<value>
```

Bearer token to seed / reuse. Default: fresh `kgi_bt_...` token per boot.

**Values**

- **Read by:** the server
- **Required:** no
- **Default:** none: a new `kgi_bt_` token is generated at each start and printed in the startup banner

**Example**

```sh
KINDGI_API_TOKEN=kgi_bt_my_stable_local_token
```

**See also**

- [Security](https://docs.kindgi.com/v0.1.8/concepts/security.md)
- [Run with authorization](https://docs.kindgi.com/v0.1.8/deploy/authorization.md)
- [Deploy on Azure Container Apps](https://docs.kindgi.com/v0.1.8/deploy/azure-container-apps.md)
- [Back up and restore the runtime's database](https://docs.kindgi.com/v0.1.8/deploy/back-up-and-restore.md)

### `KINDGI_SEED_USER_ID`

**Synopsis**

```sh signature
KINDGI_SEED_USER_ID=<value>
```

The user the runtime's API token (`KINDGI_API_TOKEN`) acts as, made a tenant admin at every boot. Unset: the user that token had at an earlier boot (kept by the token's hash), or a new one the first time a token is used. Set it to keep one user across token changes.

**Values**

- **Read by:** the server
- **Required:** no
- **Default:** none: the user the token had at an earlier start, else a new user

**Example**

```sh
KINDGI_SEED_USER_ID=00000000-0000-4000-8000-000000000002
```

**See also**

- [Run with authorization](https://docs.kindgi.com/v0.1.8/deploy/authorization.md)
- [Deploy on Google Cloud Run](https://docs.kindgi.com/v0.1.8/deploy/cloud-run.md)
- [Operate a self-hosted runtime](https://docs.kindgi.com/v0.1.8/deploy/operate.md)
- [People, API keys and service accounts](https://docs.kindgi.com/v0.1.8/deploy/people-and-keys.md)

### `KINDGI_DOCS`

**Synopsis**

```sh signature
KINDGI_DOCS=true|false|1|0
```

Mount `/docs` (Scalar API reference). `true` (default) | `false`.

**Values**

- **Read by:** the server
- **Required:** no
- **Values:** `true`, `false`, `1`, `0`
- **Default:** `true`

**Example**

```sh
KINDGI_DOCS=true
```

**See also**

- [Self-host Kindgi](https://docs.kindgi.com/v0.1.8/deploy/self-host.md)
- [Operate a self-hosted runtime](https://docs.kindgi.com/v0.1.8/deploy/operate.md)

### `KINDGI_DEV`

**Synopsis**

```sh signature
KINDGI_DEV=true|false|1|0
```

Development mode: `true` enables development-only settings (the `dotenv` secrets backend, `KINDGI_PACK_DIR`, `KINDGI_DEV_CONSOLE_LOGIN`, `KINDGI_DEV_HOST_ALIAS`). The server refuses those settings when this is off. Never set it in production.

**Values**

- **Read by:** the server
- **Required:** no
- **Values:** `true`, `false`, `1`, `0`
- **Default:** `false`

**Example**

```sh
KINDGI_DEV=false
```

**See also**

- [Deploy on Google Cloud Run](https://docs.kindgi.com/v0.1.8/deploy/cloud-run.md)
- [Serve many tenants](https://docs.kindgi.com/v0.1.8/deploy/serve-many-tenants.md)

### `KINDGI_TENANT_HOST_ACCESS`

**Synopsis**

```sh signature
KINDGI_TENANT_HOST_ACCESS=local|deployed|isolated
```

How far what tenants register may reach into the server's own host. `deployed` refuses a `stdio` MCP endpoint (a command the server would run in its container, as its user) at registration (`403 host-access-denied`) and at connect; run MCP servers over HTTP instead. It also refuses the server's connections to the cloud metadata endpoints (link-local addresses, and the few that clouds serve outside that range, such as Azure's 168.63.129.16) and to its own host (loopback) when a tenant chose the host: an MCP endpoint, a model provider's base URL, an HTTP tool, an image reference. Private networks stay reachable. `isolated` is for a server whose tenants don't trust one another: it refuses all that `deployed` does, plus private networks (RFC 1918, CGNAT 100.64.0.0/10, IPv6 unique-local, IPv6 site-local), and the server's own cloud credentials: a model provider can't sign in as the server (a Gemini provider on Vertex AI needs `secret_ref` rather than Application Default Credentials, and Azure OpenAI's `auth: entra` and Bedrock's `auth: aws-identity` are refused). The server won't start with it and `KINDGI_DEV`, `KINDGI_WEBHOOK_PRIVATE_NETWORKS=allow` or `KINDGI_AUTH_PRIVATE_IDP_ORIGINS`. `KINDGI_TENANCY=multi` starts only with it. `isolated` is used from runtime 0.1.8; earlier runtimes refuse it at startup. `local` allows all of it: only for a machine where every token holder may run commands. Unset: `local` with `KINDGI_DEV`, `deployed` otherwise; a value set here always wins.

**Values**

- **Read by:** the server
- **Required:** no
- **Values:** `local`, `deployed`, `isolated`
- **Default:** `local` when `KINDGI_DEV` is on, else `deployed`

**Example**

```sh
KINDGI_TENANT_HOST_ACCESS=deployed
```

**See also**

- [Security](https://docs.kindgi.com/v0.1.8/concepts/security.md)
- [Operate a self-hosted runtime](https://docs.kindgi.com/v0.1.8/deploy/operate.md)
- [Self-host Kindgi](https://docs.kindgi.com/v0.1.8/deploy/self-host.md)
- [Use an MCP server's tools](https://docs.kindgi.com/v0.1.8/guides/tools/mcp-servers.md)

### `KINDGI_WEBHOOK_PRIVATE_NETWORKS`

**Synopsis**

```sh signature
KINDGI_WEBHOOK_PRIVATE_NETWORKS=allow|deny
```

Whether a webhook may go to a private network address: for a receiver on the same VPC or compose network as a self-hosted server. `allow` accepts a receiver whose host resolves to a private address: RFC 1918, CGNAT (100.64.0.0/10), IPv6 unique-local or IPv6 site-local. Loopback, the cloud metadata endpoints (link-local addresses, and the few that clouds serve elsewhere, some inside CGNAT or IPv6 unique-local) and unroutable addresses stay refused, even with `allow`, and webhooks stay https only. Unset or `deny`: public addresses only. Development mode (`KINDGI_DEV`) allows http and every address anyway. `KINDGI_TENANT_HOST_ACCESS=isolated` refuses `allow`.

**Values**

- **Read by:** the server
- **Required:** no
- **Values:** `allow`, `deny`
- **Default:** `deny`

**Example**

```sh
KINDGI_WEBHOOK_PRIVATE_NETWORKS=allow
```

**See also**

- [Self-host Kindgi](https://docs.kindgi.com/v0.1.8/deploy/self-host.md)
- [Operate a self-hosted runtime](https://docs.kindgi.com/v0.1.8/deploy/operate.md)

### `KINDGI_PRODUCTION_GATE`

**Synopsis**

```sh signature
KINDGI_PRODUCTION_GATE=on|off
```

Used from runtime 0.1.8; earlier runtimes ignore it. Whether putting a project in production needs the tenant to be allowed it, for example once it has paid. `on`: `PUT /v1/projects/{projectId}/production` refuses with `403 production-not-allowed` while the tenant isn't allowed (its `productionAllowed` is `false`, set through the system API); a project already in production stays so. A tenant is allowed unless it's switched off, so turning the gate on keeps existing tenants' production. Unset or `off`: no gate, and any tenant admin can put a project in production. `KINDGI_TENANCY=multi` starts only with `on`.

**Values**

- **Read by:** the server
- **Required:** no
- **Values:** `on`, `off`
- **Default:** `off`: no gate, and any tenant admin can put a project in production

**Example**

```sh
KINDGI_PRODUCTION_GATE=on
```

**See also**

- [Self-host Kindgi](https://docs.kindgi.com/v0.1.8/deploy/self-host.md)
- [Operate a self-hosted runtime](https://docs.kindgi.com/v0.1.8/deploy/operate.md)

### `KINDGI_PRODUCTION_NEXT_URL`

**Synopsis**

```sh signature
KINDGI_PRODUCTION_NEXT_URL=<value>
```

Used from runtime 0.1.8; earlier runtimes ignore it. Where a tenant goes to put more projects in production, for example a billing page: an https URL. It's the `details.next` of two refusals: `production-not-allowed`, for a tenant that isn't allowed to put projects in production, and `production-limit-reached`, for one at its limit on projects in production. It's also `GET /v1/tenant`'s `productionNext` while the tenant isn't allowed. Unset: the first refusal says to ask whoever runs the server, and the second to take a project out of production or ask whoever runs the server to raise the limit.

**Values**

- **Read by:** the server
- **Required:** no
- **Default:** none: `production-not-allowed` says to ask whoever runs the server, and `production-limit-reached` to take a project out of production or ask whoever runs the server to raise the limit

**Example**

```sh
KINDGI_PRODUCTION_NEXT_URL=https://platform.example.com/account/billing
```

**See also**

- [Serve many tenants](https://docs.kindgi.com/v0.1.8/deploy/serve-many-tenants.md)
- [Organize work by org and project](https://docs.kindgi.com/v0.1.8/guides/projects/organize-by-org-and-project.md)

### `KINDGI_USAGE_METERING`

**Synopsis**

```sh signature
KINDGI_USAGE_METERING=on|off
```

Used from runtime 0.1.8; earlier runtimes ignore it. Whether the server counts usage. `on`: it records each run when it's created (with its class: `production`, `staging`, `dry-run`, `replay`, `eval` or `child`) and when it ends, each approval asked for and decided, each model call, and each project put in production or taken out. Each is counted exactly once, in the same transaction as what it counts. `/v1/cost/*` reads a tenant's own counts. Unset or `off`: model calls are counted as before, and nothing else. `KINDGI_TENANCY=multi` starts only with `on`.

**Values**

- **Read by:** the server
- **Required:** no
- **Values:** `on`, `off`
- **Default:** `off`: model calls are counted, nothing else

**Example**

```sh
KINDGI_USAGE_METERING=on
```

**See also**

- [Self-host Kindgi](https://docs.kindgi.com/v0.1.8/deploy/self-host.md)
- [Operate a self-hosted runtime](https://docs.kindgi.com/v0.1.8/deploy/operate.md)

### `KINDGI_LICENSE_KEY`

**Synopsis**

```sh signature
KINDGI_LICENSE_KEY=<value>
```

The commercial license key (`kgi_lk_...`) Kindgi issues: signed, checked offline at startup, with no call to Kindgi. Required outside development mode: without a valid key the server refuses to start, naming this variable. `KINDGI_DEV=true` needs none. A production key comes with a commercial license; a free non-production key covers staging and CI. A secret: give it from your secrets store.

**Values**

- **Read by:** the server
- **Required:** no
- **Default:** none: outside development mode the server refuses to start; development mode needs none

**Example**

```sh
KINDGI_LICENSE_KEY=kgi_lk_<payload>.<signature>
```

**See also**

- [Licensing](https://docs.kindgi.com/v0.1.8/concepts/licensing.md)
- [Rotate the runtime's tokens and keys](https://docs.kindgi.com/v0.1.8/deploy/rotate-keys.md)
- [Self-host Kindgi](https://docs.kindgi.com/v0.1.8/deploy/self-host.md)

### `KINDGI_LICENSE_KEY_REF`

**Synopsis**

```sh signature
KINDGI_LICENSE_KEY_REF=<value>
```

Where this deployment keeps its license key, as `kindgi license renew --key` names it (`gcp:projects/<p>/secrets/<name>`, `azure:https://<vault>.vault.azure.net/secrets/<name>`, `aws:<region>:<name>`, `env-file:<path>#<NAME>`). Only the expiry warnings read it, to print the exact renew command; the deployment modules set it. Not the key: the key is `KINDGI_LICENSE_KEY`.

**Values**

- **Read by:** the server
- **Required:** no
- **Default:** none: expiry warnings print a placeholder in the renew command

**Example**

```sh
KINDGI_LICENSE_KEY_REF=gcp:projects/acme-prod/secrets/kindgi-license-key
```

**See also**

- [Renew the license key](https://docs.kindgi.com/v0.1.8/deploy/renew-the-license-key.md)

### `KINDGI_LICENSE_RENEWER_REF`

**Synopsis**

```sh signature
KINDGI_LICENSE_RENEWER_REF=<value>
```

Where this deployment keeps its renewer's key, as `kindgi license renew --renewer` names it. Only the expiry warnings read it, to print the exact renew command; the deployment modules set it.

**Values**

- **Read by:** the server
- **Required:** no
- **Default:** none: expiry warnings print a placeholder in the renew command

**Example**

```sh
KINDGI_LICENSE_RENEWER_REF=gcp:projects/acme-prod/secrets/kindgi-license-renewer
```

**See also**

- [Renew the license key](https://docs.kindgi.com/v0.1.8/deploy/renew-the-license-key.md)

### `KINDGI_ENV`

**Synopsis**

```sh signature
KINDGI_ENV=<value>
```

The env this runtime serves. The secrets and env values a tool declares by name (`needsSpec.secrets`, `needsSpec.env`) resolve under this env name. Unset: `local` in development mode (secrets from the `.env` and `.env.local` files, env values from `/v1/env`); otherwise a tool that declares either fails its calls, naming this variable.

**Values**

- **Read by:** the server
- **Required:** no
- **Default:** none: `local` in development mode, else no env (a tool that declares secrets or env values fails its calls)

**Example**

```sh
KINDGI_ENV=production
```

**See also**

- [Operate a self-hosted runtime](https://docs.kindgi.com/v0.1.8/deploy/operate.md)
- [Self-host Kindgi](https://docs.kindgi.com/v0.1.8/deploy/self-host.md)
- [Start a run](https://docs.kindgi.com/v0.1.8/guides/runs/start-a-run.md)
- [Store a secret](https://docs.kindgi.com/v0.1.8/guides/secrets/store-a-secret.md)

### `KINDGI_PUBLIC_TOKEN_SIGNING_KEY_PATH`

**Synopsis**

```sh signature
KINDGI_PUBLIC_TOKEN_SIGNING_KEY_PATH=<value>
```

Absolute path to the Ed25519 private key (PKCS#8 PEM, mode 0600) that signs public run tokens (`kgi_pt_…`): the short-lived, read-only tokens a browser uses to follow a run. Use a key for this alone (`openssl genpkey -algorithm ed25519`). Unset: in development mode the server signs with a key generated at startup (tokens end at restart); otherwise public run tokens are off.

**Values**

- **Read by:** the server
- **Required:** no
- **Default:** none: in development mode a key generated at start; otherwise public run tokens are off

**Example**

```sh
KINDGI_PUBLIC_TOKEN_SIGNING_KEY_PATH=/etc/kindgi/public-token-signing.pem
```

**See also**

- [Security](https://docs.kindgi.com/v0.1.8/concepts/security.md)
- [Follow a run from the browser](https://docs.kindgi.com/v0.1.8/guides/runs/follow-from-the-browser.md)

### `KINDGI_PUBLIC_TOKEN_SIGNING_KEY`

**Synopsis**

```sh signature
KINDGI_PUBLIC_TOKEN_SIGNING_KEY=<value>
```

The same key's PEM file, base64 (`base64 < key.pem`): for platforms that give secrets as environment variables (Cloud Run with Secret Manager), where a key file's mode can't be 0600. Set this or `KINDGI_PUBLIC_TOKEN_SIGNING_KEY_PATH`, not both.

**Values**

- **Read by:** the server
- **Required:** no
- **Default:** none: same as `KINDGI_PUBLIC_TOKEN_SIGNING_KEY_PATH` (development key, or public run tokens off)

**Example**

```sh
# base64 of an Ed25519 private key in PKCS#8 PEM
KINDGI_PUBLIC_TOKEN_SIGNING_KEY="$(openssl genpkey -algorithm ed25519 | base64 | tr -d '\n')"
```

**See also**

- [Self-host Kindgi](https://docs.kindgi.com/v0.1.8/deploy/self-host.md)
- [Operate a self-hosted runtime](https://docs.kindgi.com/v0.1.8/deploy/operate.md)

### `KINDGI_AUTH_SECRET_PATH`

**Synopsis**

```sh signature
KINDGI_AUTH_SECRET_PATH=<value>
```

Turns on sign-in with identity providers (OIDC, SAML): absolute path to a 32-byte random key (mode 0600) the browser sign-in flow signs its state with (`openssl rand 32 > auth-secret`). Needs `KINDGI_PUBLIC_URL`: identity providers send people back there. Unset (with `KINDGI_AUTH_SECRET` unset too): sign-in is off, and the API takes only API keys.

**Values**

- **Read by:** the server
- **Required:** no
- **Default:** none (with `KINDGI_AUTH_SECRET` unset): sign-in with identity providers is off

**Example**

```sh
KINDGI_AUTH_SECRET_PATH=/etc/kindgi/auth-secret
```

**See also**

- [Turn on sign-in](https://docs.kindgi.com/v0.1.8/deploy/sign-in.md)

### `KINDGI_AUTH_SECRET`

**Synopsis**

```sh signature
KINDGI_AUTH_SECRET=<value>
```

The same key, base64 (`base64 < auth-secret`): for platforms that give secrets as environment variables (Cloud Run with Secret Manager), where a key file's mode can't be 0600. Set this or `KINDGI_AUTH_SECRET_PATH`, not both.

**Values**

- **Read by:** the server
- **Required:** no
- **Default:** none (with `KINDGI_AUTH_SECRET_PATH` unset): sign-in with identity providers is off

**Example**

```sh
# 32 random bytes, base64
KINDGI_AUTH_SECRET="$(openssl rand -base64 32)"
```

**See also**

- [Deploy on Google Cloud Run](https://docs.kindgi.com/v0.1.8/deploy/cloud-run.md)
- [Turn on sign-in](https://docs.kindgi.com/v0.1.8/deploy/sign-in.md)

### `KINDGI_AUTH_PRIVATE_IDP_ORIGINS`

**Synopsis**

```sh signature
KINDGI_AUTH_PRIVATE_IDP_ORIGINS=<value>
```

Comma-separated origins of identity providers on a private network (a self-hosted Keycloak or AD FS behind a VPN, e.g. `https://sso.corp.internal`) that tenants may register. Only public HTTPS identity providers are allowed otherwise: a tenant admin can't point the runtime at the deployment's own network. `KINDGI_TENANT_HOST_ACCESS=isolated` refuses this setting, since every tenant could name a listed origin.

**Values**

- **Read by:** the server
- **Required:** no
- **Default:** none: none; only public HTTPS identity providers are allowed

**Example**

```sh
KINDGI_AUTH_PRIVATE_IDP_ORIGINS=https://sso.corp.internal
```

**See also**

- [Security](https://docs.kindgi.com/v0.1.8/concepts/security.md)
- [Operate a self-hosted runtime](https://docs.kindgi.com/v0.1.8/deploy/operate.md)
- [Serve many tenants](https://docs.kindgi.com/v0.1.8/deploy/serve-many-tenants.md)
- [Set up SSO](https://docs.kindgi.com/v0.1.8/guides/sso.md)

### `KINDGI_AUTH_VERIFIED_DOMAINS`

**Synopsis**

```sh signature
KINDGI_AUTH_VERIFIED_DOMAINS=<value>
```

Comma-separated `domain:tenant` pairs (the tenant by id, or by a unique slug): email domains whose sign-in routes to that tenant's identity providers. A runtime that serves one tenant routes that tenant's domains without this. One that serves several routes a domain only once it's listed here: otherwise a tenant could list another company's domain and catch its people. An unlisted domain's people can still use their provider's own sign-in link (`kindgi sso providers test` prints it). A malformed entry, or a tenant this runtime doesn't serve, stops the runtime at start.

**Values**

- **Read by:** the server
- **Required:** no
- **Default:** none: none; a runtime serving one tenant routes that tenant's domains without it, one serving several routes no domain

**Example**

```sh
KINDGI_AUTH_VERIFIED_DOMAINS=acme.com:3f8e2c1a-0b7d-4e9a-9c5f-2d1e6b8a7c40
```

**See also**

- [Deploy on Google Cloud Run](https://docs.kindgi.com/v0.1.8/deploy/cloud-run.md)
- [Serve many tenants](https://docs.kindgi.com/v0.1.8/deploy/serve-many-tenants.md)

### `KINDGI_AUTH_TENANT_PROVIDERS`

**Synopsis**

```sh signature
KINDGI_AUTH_TENANT_PROVIDERS=<value>
```

Whether a tenant's admins may add, change and remove its identity providers: `on` or `off`. Default `on`. `off` when the operator manages sign-in: then only the deployment's own token (`KINDGI_API_TOKEN`) can, and a tenant's change answers `403 identity-providers-operator-managed`. The providers already there keep signing people in either way, and anyone may still read them.

**Values**

- **Read by:** the server
- **Required:** no
- **Default:** `on`

**Example**

```sh
KINDGI_AUTH_TENANT_PROVIDERS=off
```

**See also**

- [Self-host Kindgi](https://docs.kindgi.com/v0.1.8/deploy/self-host.md)
- [Operate a self-hosted runtime](https://docs.kindgi.com/v0.1.8/deploy/operate.md)

### `KINDGI_AUTH_GOOGLE_CLIENT_ID`

**Synopsis**

```sh signature
KINDGI_AUTH_GOOGLE_CLIENT_ID=<value>
```

Turns on "Continue with Google": the client id of the deployment's own Google app (an OAuth client (Web application)). People who've been added to a workspace sign in with their Google account, by its verified email. The app allows the redirect URI `<KINDGI_PUBLIC_URL>/auth/kindgi/social/callback/google`. Needs sign-in on (`KINDGI_AUTH_SECRET_PATH`) and the app's secret (`KINDGI_AUTH_GOOGLE_CLIENT_SECRET` or `…_SECRET_PATH`).

**Values**

- **Read by:** the server
- **Required:** no
- **Default:** none: "Continue with Google" is off

**Example**

```sh
KINDGI_AUTH_GOOGLE_CLIENT_ID=<client id of your Google OAuth app>
```

**See also**

- [Turn on sign-in](https://docs.kindgi.com/v0.1.8/deploy/sign-in.md)

### `KINDGI_AUTH_GOOGLE_CLIENT_SECRET`

**Synopsis**

```sh signature
KINDGI_AUTH_GOOGLE_CLIENT_SECRET=<value>
```

The Google app's client secret, for platforms that give secrets as environment variables. Set this or `KINDGI_AUTH_GOOGLE_CLIENT_SECRET_PATH`, not both.

**Values**

- **Read by:** the server
- **Required:** no
- **Default:** none: no secret (the client id then needs `KINDGI_AUTH_GOOGLE_CLIENT_SECRET_PATH`)

**Example**

```sh
KINDGI_AUTH_GOOGLE_CLIENT_SECRET=<client secret of your Google OAuth app>
```

**See also**

- [Self-host Kindgi](https://docs.kindgi.com/v0.1.8/deploy/self-host.md)
- [Operate a self-hosted runtime](https://docs.kindgi.com/v0.1.8/deploy/operate.md)

### `KINDGI_AUTH_GOOGLE_CLIENT_SECRET_PATH`

**Synopsis**

```sh signature
KINDGI_AUTH_GOOGLE_CLIENT_SECRET_PATH=<value>
```

A file (mode 0600) holding the Google app's client secret. Set this or `KINDGI_AUTH_GOOGLE_CLIENT_SECRET`, not both.

**Values**

- **Read by:** the server
- **Required:** no
- **Default:** none: no secret file

**Example**

```sh
KINDGI_AUTH_GOOGLE_CLIENT_SECRET_PATH=/etc/kindgi/google-client-secret
```

**See also**

- [Turn on sign-in](https://docs.kindgi.com/v0.1.8/deploy/sign-in.md)

### `KINDGI_AUTH_MICROSOFT_CLIENT_ID`

**Synopsis**

```sh signature
KINDGI_AUTH_MICROSOFT_CLIENT_ID=<value>
```

Turns on "Continue with Microsoft": the client id of the deployment's own Microsoft app (an app registration (multitenant, with the ID-token optional claims `email` and `xms_edov`)). People who've been added to a workspace sign in with their Microsoft account, by its verified email. The app allows the redirect URI `<KINDGI_PUBLIC_URL>/auth/kindgi/social/callback/microsoft`. Needs sign-in on (`KINDGI_AUTH_SECRET_PATH`) and the app's secret (`KINDGI_AUTH_MICROSOFT_CLIENT_SECRET` or `…_SECRET_PATH`).

**Values**

- **Read by:** the server
- **Required:** no
- **Default:** none: "Continue with Microsoft" is off

**Example**

```sh
KINDGI_AUTH_MICROSOFT_CLIENT_ID=<application id of your Microsoft app>
```

**See also**

- [Self-host Kindgi](https://docs.kindgi.com/v0.1.8/deploy/self-host.md)
- [Operate a self-hosted runtime](https://docs.kindgi.com/v0.1.8/deploy/operate.md)

### `KINDGI_AUTH_MICROSOFT_CLIENT_SECRET`

**Synopsis**

```sh signature
KINDGI_AUTH_MICROSOFT_CLIENT_SECRET=<value>
```

The Microsoft app's client secret, for platforms that give secrets as environment variables. Set this or `KINDGI_AUTH_MICROSOFT_CLIENT_SECRET_PATH`, not both.

**Values**

- **Read by:** the server
- **Required:** no
- **Default:** none: no secret (the client id then needs `KINDGI_AUTH_MICROSOFT_CLIENT_SECRET_PATH`)

**Example**

```sh
KINDGI_AUTH_MICROSOFT_CLIENT_SECRET=<client secret of your Microsoft app>
```

**See also**

- [Self-host Kindgi](https://docs.kindgi.com/v0.1.8/deploy/self-host.md)
- [Operate a self-hosted runtime](https://docs.kindgi.com/v0.1.8/deploy/operate.md)

### `KINDGI_AUTH_MICROSOFT_CLIENT_SECRET_PATH`

**Synopsis**

```sh signature
KINDGI_AUTH_MICROSOFT_CLIENT_SECRET_PATH=<value>
```

A file (mode 0600) holding the Microsoft app's client secret. Set this or `KINDGI_AUTH_MICROSOFT_CLIENT_SECRET`, not both.

**Values**

- **Read by:** the server
- **Required:** no
- **Default:** none: no secret file

**Example**

```sh
KINDGI_AUTH_MICROSOFT_CLIENT_SECRET_PATH=/etc/kindgi/microsoft-client-secret
```

**See also**

- [Self-host Kindgi](https://docs.kindgi.com/v0.1.8/deploy/self-host.md)
- [Operate a self-hosted runtime](https://docs.kindgi.com/v0.1.8/deploy/operate.md)

### `KINDGI_AUTH_GITHUB_CLIENT_ID`

**Synopsis**

```sh signature
KINDGI_AUTH_GITHUB_CLIENT_ID=<value>
```

Turns on "Continue with GitHub": the client id of the deployment's own GitHub app (an OAuth App). People who've been added to a workspace sign in with their GitHub account, by its verified email. The app allows the redirect URI `<KINDGI_PUBLIC_URL>/auth/kindgi/social/callback/github`. Needs sign-in on (`KINDGI_AUTH_SECRET_PATH`) and the app's secret (`KINDGI_AUTH_GITHUB_CLIENT_SECRET` or `…_SECRET_PATH`).

**Values**

- **Read by:** the server
- **Required:** no
- **Default:** none: "Continue with GitHub" is off

**Example**

```sh
KINDGI_AUTH_GITHUB_CLIENT_ID=<client id of your GitHub OAuth app>
```

**See also**

- [Self-host Kindgi](https://docs.kindgi.com/v0.1.8/deploy/self-host.md)
- [Operate a self-hosted runtime](https://docs.kindgi.com/v0.1.8/deploy/operate.md)

### `KINDGI_AUTH_GITHUB_CLIENT_SECRET`

**Synopsis**

```sh signature
KINDGI_AUTH_GITHUB_CLIENT_SECRET=<value>
```

The GitHub app's client secret, for platforms that give secrets as environment variables. Set this or `KINDGI_AUTH_GITHUB_CLIENT_SECRET_PATH`, not both.

**Values**

- **Read by:** the server
- **Required:** no
- **Default:** none: no secret (the client id then needs `KINDGI_AUTH_GITHUB_CLIENT_SECRET_PATH`)

**Example**

```sh
KINDGI_AUTH_GITHUB_CLIENT_SECRET=<client secret of your GitHub OAuth app>
```

**See also**

- [Self-host Kindgi](https://docs.kindgi.com/v0.1.8/deploy/self-host.md)
- [Operate a self-hosted runtime](https://docs.kindgi.com/v0.1.8/deploy/operate.md)

### `KINDGI_AUTH_GITHUB_CLIENT_SECRET_PATH`

**Synopsis**

```sh signature
KINDGI_AUTH_GITHUB_CLIENT_SECRET_PATH=<value>
```

A file (mode 0600) holding the GitHub app's client secret. Set this or `KINDGI_AUTH_GITHUB_CLIENT_SECRET`, not both.

**Values**

- **Read by:** the server
- **Required:** no
- **Default:** none: no secret file

**Example**

```sh
KINDGI_AUTH_GITHUB_CLIENT_SECRET_PATH=/etc/kindgi/github-client-secret
```

**See also**

- [Self-host Kindgi](https://docs.kindgi.com/v0.1.8/deploy/self-host.md)
- [Operate a self-hosted runtime](https://docs.kindgi.com/v0.1.8/deploy/operate.md)

### `KINDGI_AUTH_EMAIL_SMTP_URL`

**Synopsis**

```sh signature
KINDGI_AUTH_EMAIL_SMTP_URL=<value>
```

Turns on the emailed sign-in link: people who've been added to a workspace can ask for a one-time link (ten minutes) by email. The SMTP server to send it through, with its credentials: `smtps://user:password@smtp.example.com:465`. Any provider works (Resend, Postmark, Amazon SES, your own relay). Needs sign-in on (`KINDGI_AUTH_SECRET_PATH`) and `KINDGI_AUTH_EMAIL_FROM`. Set this or `KINDGI_AUTH_EMAIL_SMTP_URL_PATH`, not both.

**Values**

- **Read by:** the server
- **Required:** no
- **Default:** none (with `KINDGI_AUTH_EMAIL_SMTP_URL_PATH` unset): the emailed sign-in link is off

**Example**

```sh
KINDGI_AUTH_EMAIL_SMTP_URL=smtps://<user>:<password>@smtp.example.com:465
```

**See also**

- [Decide an approval](https://docs.kindgi.com/v0.1.8/guides/approvals/decide-an-approval.md)

### `KINDGI_AUTH_EMAIL_SMTP_URL_PATH`

**Synopsis**

```sh signature
KINDGI_AUTH_EMAIL_SMTP_URL_PATH=<value>
```

A file (mode 0600) holding the SMTP URL with its credentials. Set this or `KINDGI_AUTH_EMAIL_SMTP_URL`, not both.

**Values**

- **Read by:** the server
- **Required:** no
- **Default:** none: no SMTP URL file

**Example**

```sh
KINDGI_AUTH_EMAIL_SMTP_URL_PATH=/etc/kindgi/smtp-url
```

**See also**

- [Turn on sign-in](https://docs.kindgi.com/v0.1.8/deploy/sign-in.md)

### `KINDGI_REVIEWER_EMAIL`

**Synopsis**

```sh signature
KINDGI_REVIEWER_EMAIL=<value>
```

`on` emails reviewers when an approval waits for them: the people the approvals inbox would show it to (its assigned reviewer, else every active reviewer of its required role or above), and only those who may read its project. The first email goes at once; anything more for the same person within five minutes comes as one digest. Each email has the approval's title, project, required role and a link, never what it's about (that stays behind sign-in). Sent through the emailed sign-in link's server, so it needs `KINDGI_AUTH_EMAIL_SMTP_URL` (or `_PATH`) and `KINDGI_AUTH_EMAIL_FROM`; turned on without them, the runtime refuses to start. Off by default: emailing people is the operator's choice.

**Values**

- **Read by:** the server
- **Required:** no
- **Default:** `off`

**Example**

```sh
KINDGI_REVIEWER_EMAIL=on
```

**See also**

- [Operate a self-hosted runtime](https://docs.kindgi.com/v0.1.8/deploy/operate.md)

### `KINDGI_AUTH_EMAIL_FROM`

**Synopsis**

```sh signature
KINDGI_AUTH_EMAIL_FROM=<value>
```

The emailed sign-in link's From address, on a domain your SMTP provider may send for (SPF and DKIM set up): `Kindgi <sign-in@acme.com>`.

**Values**

- **Read by:** the server
- **Required:** no
- **Default:** none: the emailed sign-in link is off (required with the SMTP URL)

**Example**

```sh
KINDGI_AUTH_EMAIL_FROM=Kindgi <sign-in@acme.com>
```

**See also**

- [Turn on sign-in](https://docs.kindgi.com/v0.1.8/deploy/sign-in.md)
- [Decide an approval](https://docs.kindgi.com/v0.1.8/guides/approvals/decide-an-approval.md)

### `KINDGI_AUTH_EMAIL_LINK_DAILY_CAP`

**Synopsis**

```sh signature
KINDGI_AUTH_EMAIL_LINK_DAILY_CAP=<value>
```

The emailed sign-in link's backstop: at most this many links a day to one address from one client network (IPv4 /24, IPv6 /64). Default 20. Before it: one a minute and 3 per 15 minutes to an address, except for the browser that already got a link for it, so someone else's requests can't keep its person out. At a limit the request answers as usual, and nothing is sent.

**Values**

- **Read by:** the server
- **Required:** no
- **Default:** `20` links a day (a whole number from 1 to 1000)

**Example**

```sh
KINDGI_AUTH_EMAIL_LINK_DAILY_CAP=20
```

**See also**

- [Turn on sign-in](https://docs.kindgi.com/v0.1.8/deploy/sign-in.md)

### `KINDGI_AUTH_TURNSTILE_SECRET`

**Synopsis**

```sh signature
KINDGI_AUTH_TURNSTILE_SECRET=<value>
```

A Cloudflare Turnstile secret key: asking for an emailed link then needs the widget's token, checked with Cloudflare. Required when the runtime serves several tenants. Without it (one tenant), the link is still sent only to people who can sign in, within the per-address limits (see `KINDGI_AUTH_EMAIL_LINK_DAILY_CAP`), and requests are rate-limited per client. Set this or `KINDGI_AUTH_TURNSTILE_SECRET_PATH`, not both, with `KINDGI_AUTH_TURNSTILE_SITE_KEY`.

**Values**

- **Read by:** the server
- **Required:** no
- **Default:** none (with `KINDGI_AUTH_TURNSTILE_SECRET_PATH` unset): no Turnstile check on emailed-link requests

**Example**

```sh
KINDGI_AUTH_TURNSTILE_SECRET=<Cloudflare Turnstile secret key>
```

**See also**

- [Deploy on Google Cloud Run](https://docs.kindgi.com/v0.1.8/deploy/cloud-run.md)

### `KINDGI_AUTH_TURNSTILE_SECRET_PATH`

**Synopsis**

```sh signature
KINDGI_AUTH_TURNSTILE_SECRET_PATH=<value>
```

A file (mode 0600) holding the Turnstile secret key. Set this or `KINDGI_AUTH_TURNSTILE_SECRET`, not both.

**Values**

- **Read by:** the server
- **Required:** no
- **Default:** none: no Turnstile secret file

**Example**

```sh
KINDGI_AUTH_TURNSTILE_SECRET_PATH=/etc/kindgi/turnstile-secret
```

**See also**

- [Self-host Kindgi](https://docs.kindgi.com/v0.1.8/deploy/self-host.md)
- [Operate a self-hosted runtime](https://docs.kindgi.com/v0.1.8/deploy/operate.md)

### `KINDGI_AUTH_TURNSTILE_SITE_KEY`

**Synopsis**

```sh signature
KINDGI_AUTH_TURNSTILE_SITE_KEY=<value>
```

The Turnstile site key the sign-in page shows the widget with (public). Set with the secret.

**Values**

- **Read by:** the server
- **Required:** no
- **Default:** none: no Turnstile widget

**Example**

```sh
KINDGI_AUTH_TURNSTILE_SITE_KEY=<Cloudflare Turnstile site key>
```

**See also**

- [Self-host Kindgi](https://docs.kindgi.com/v0.1.8/deploy/self-host.md)
- [Operate a self-hosted runtime](https://docs.kindgi.com/v0.1.8/deploy/operate.md)

### `KINDGI_TRUSTED_PROXIES`

**Synopsis**

```sh signature
KINDGI_TRUSTED_PROXIES=<value>
```

Which proxies in front of the runtime to trust for the client's address, which rate limits and audit records use. Unset: the connection's peer, and `X-Forwarded-For` is ignored (anyone can send it). A hop count (`1` behind one proxy such as a cloud load balancer or ingress, `2` behind two) or comma-separated IPs/CIDR ranges of your proxies: the client is the first `X-Forwarded-For` hop, from the right, that isn't one of them; never the leftmost on its own. Behind a proxy without this, every client counts as the proxy, so rate limits are shared by everyone.

**Values**

- **Read by:** the server
- **Required:** no
- **Default:** none: no trusted proxies; the connection's peer is the client address and `X-Forwarded-For` is ignored

**Example**

```sh
KINDGI_TRUSTED_PROXIES=1
```

**See also**

- [Deploy on Google Cloud Run](https://docs.kindgi.com/v0.1.8/deploy/cloud-run.md)
- [Operate a self-hosted runtime](https://docs.kindgi.com/v0.1.8/deploy/operate.md)
- [Self-host Kindgi](https://docs.kindgi.com/v0.1.8/deploy/self-host.md)
- [Turn on sign-in](https://docs.kindgi.com/v0.1.8/deploy/sign-in.md)

### `KINDGI_SESSION_TTL_MS`

**Synopsis**

```sh signature
KINDGI_SESSION_TTL_MS=<value>
```

A browser session's absolute lifetime, in milliseconds: the person signs in again after it. Default 43200000 (12 hours); at least 60000.

**Values**

- **Read by:** the server
- **Required:** no
- **Default:** `43200000` (12 hours); at least `60000`

**Example**

```sh
KINDGI_SESSION_TTL_MS=43200000
```

**See also**

- [Turn on sign-in](https://docs.kindgi.com/v0.1.8/deploy/sign-in.md)
- [Sessions](https://docs.kindgi.com/v0.1.8/guides/sso/sessions.md)

### `KINDGI_SESSION_IDLE_TIMEOUT_MS`

**Synopsis**

```sh signature
KINDGI_SESSION_IDLE_TIMEOUT_MS=<value>
```

How long a browser session may sit idle before it ends, in milliseconds. Default 3600000 (60 minutes); at least 60000, and no longer than `KINDGI_SESSION_TTL_MS`.

**Values**

- **Read by:** the server
- **Required:** no
- **Default:** `3600000` (60 minutes); at least `60000`, at most the session lifetime

**Example**

```sh
KINDGI_SESSION_IDLE_TIMEOUT_MS=3600000
```

**See also**

- [Turn on sign-in](https://docs.kindgi.com/v0.1.8/deploy/sign-in.md)
- [Sessions](https://docs.kindgi.com/v0.1.8/guides/sso/sessions.md)

### `KINDGI_CONSOLE_TOKEN_SIGN_IN`

**Synopsis**

```sh signature
KINDGI_CONSOLE_TOKEN_SIGN_IN=<value>
```

Whether a person may sign in to the console with an API token: `on` or `off`. Default `off`, and `on` in local development (`kindgi dev`). The token is exchanged once for a browser session (the same cookie as sign-in with an identity provider) and never kept in the browser. Only a person's full key opens a session, never a service account's or a narrowed key. API tokens work for the API, CLI and SDKs either way. Set `on` to keep signing in to the console by pasting a token when it has no identity provider.

**Values**

- **Read by:** the server
- **Required:** no
- **Default:** `off`, and `on` when `KINDGI_DEV` is on

**Example**

```sh
KINDGI_CONSOLE_TOKEN_SIGN_IN=on
```

**See also**

- [Self-host Kindgi](https://docs.kindgi.com/v0.1.8/deploy/self-host.md)
- [Operate a self-hosted runtime](https://docs.kindgi.com/v0.1.8/deploy/operate.md)

### `KINDGI_EXPORT_SIGNING_KEY_PATH`

**Synopsis**

```sh signature
KINDGI_EXPORT_SIGNING_KEY_PATH=<value>
```

Absolute path to the private key (PKCS#8 PEM, mode 0600) that signs exports: approval audit bundles, run provenance and compliance evidence. An Ed25519 key signs `ed25519` (`openssl genpkey -algorithm ed25519`); an EC P-256 key signs `ecdsa-p256-sha256`. Use a key for this alone; `GET /v1/export-signing-keys` publishes its public half. Set one of this, `KINDGI_EXPORT_SIGNING_KEY` or `KINDGI_EXPORT_SIGNING_KMS_KEY`. None: in development mode the server signs with a key generated at startup; otherwise exports answer `404 signing-not-configured`.

**Values**

- **Read by:** the server
- **Required:** no
- **Default:** none (with the other two export key variables unset): in development mode a key generated at start; otherwise exports answer `404 signing-not-configured`

**Example**

```sh
KINDGI_EXPORT_SIGNING_KEY_PATH=/etc/kindgi/export-signing.pem
```

**See also**

- [Operate a self-hosted runtime](https://docs.kindgi.com/v0.1.8/deploy/operate.md)
- [Export signed evidence](https://docs.kindgi.com/v0.1.8/guides/observability/export-signed-evidence.md)

### `KINDGI_EXPORT_SIGNING_KEY`

**Synopsis**

```sh signature
KINDGI_EXPORT_SIGNING_KEY=<value>
```

The same key's PEM file, base64 (`base64 < key.pem`): for platforms that give secrets as environment variables, such as Cloud Run with Secret Manager. A production path in its own right.

**Values**

- **Read by:** the server
- **Required:** no
- **Default:** none: same as `KINDGI_EXPORT_SIGNING_KEY_PATH`

**Example**

```sh
# base64 of an Ed25519 or EC P-256 private key in PKCS#8 PEM
KINDGI_EXPORT_SIGNING_KEY="$(openssl genpkey -algorithm ed25519 | base64 | tr -d '\n')"
```

**See also**

- [Operate a self-hosted runtime](https://docs.kindgi.com/v0.1.8/deploy/operate.md)
- [Export signed evidence](https://docs.kindgi.com/v0.1.8/guides/observability/export-signed-evidence.md)

### `KINDGI_EXPORT_SIGNING_KMS_KEY`

**Synopsis**

```sh signature
KINDGI_EXPORT_SIGNING_KMS_KEY=<value>
```

Optional: a KMS key version that signs exports, so the private key never leaves the KMS. Its shape picks the KMS. **Cloud KMS:** `projects/<p>/locations/<l>/keyRings/<r>/cryptoKeys/<k>/cryptoKeyVersions/<n>`, an `EC_SIGN_ED25519` key (it signs `ed25519`) or an `EC_SIGN_P256_SHA256` key (`ecdsa-p256-sha256`); the server's service account needs `roles/cloudkms.signerVerifier` on it (and `roles/cloudkms.publicKeyViewer`, to read its public key at boot). **Azure Key Vault:** `https://<vault>.vault.azure.net/keys/<name>/<version>`, with its version, an EC P-256 key allowed to sign (`ecdsa-p256-sha256`: Key Vault has no Ed25519); the server's identity needs Key Vault Crypto User on it. Any other value stops the server at boot, naming both shapes.

**Values**

- **Read by:** the server
- **Required:** no
- **Default:** none: the export key comes from `KINDGI_EXPORT_SIGNING_KEY_PATH` or `KINDGI_EXPORT_SIGNING_KEY`

**Example**

```sh
KINDGI_EXPORT_SIGNING_KMS_KEY=projects/acme/locations/global/keyRings/kindgi/cryptoKeys/exports/cryptoKeyVersions/1
```

**See also**

- [Operate a self-hosted runtime](https://docs.kindgi.com/v0.1.8/deploy/operate.md)
- [Export signed evidence](https://docs.kindgi.com/v0.1.8/guides/observability/export-signed-evidence.md)

### `KINDGI_EXPORT_SIGNING_RETIRED_PUBLIC_KEYS_PATH`

**Synopsis**

```sh signature
KINDGI_EXPORT_SIGNING_RETIRED_PUBLIC_KEYS_PATH=<value>
```

Absolute path to a file of one or more PEM public keys (`-----BEGIN PUBLIC KEY-----`, concatenated): export keys this deployment signed with before a rotation. `GET /v1/export-signing-keys` lists them after the active key, with `active: false`, so `kindgi exports verify --from-runtime` still trusts what they signed; they never sign. Ed25519 or EC P-256 keys, public halves only: a private key in the file stops the server at boot. Works with any signing key, file or KMS. Set this or `KINDGI_EXPORT_SIGNING_RETIRED_PUBLIC_KEYS`, not both.

**Values**

- **Read by:** the server
- **Required:** no
- **Default:** none: no retired export keys are listed

**Example**

```sh
KINDGI_EXPORT_SIGNING_RETIRED_PUBLIC_KEYS_PATH=/etc/kindgi/export-signing-retired.pem
```

**See also**

- [Export signed evidence](https://docs.kindgi.com/v0.1.8/guides/observability/export-signed-evidence.md)

### `KINDGI_EXPORT_SIGNING_RETIRED_PUBLIC_KEYS`

**Synopsis**

```sh signature
KINDGI_EXPORT_SIGNING_RETIRED_PUBLIC_KEYS=<value>
```

The same file's content, base64 (`base64 < retired.pem`): for platforms that give settings as environment variables, such as Cloud Run.

**Values**

- **Read by:** the server
- **Required:** no
- **Default:** none: no retired export keys are listed

**Example**

```sh
# base64 of a file of PEM public keys
KINDGI_EXPORT_SIGNING_RETIRED_PUBLIC_KEYS="$(openssl pkey -in old-key.pem -pubout | base64 | tr -d '\n')"
```

**See also**

- [Export signed evidence](https://docs.kindgi.com/v0.1.8/guides/observability/export-signed-evidence.md)

### `KINDGI_CORS_ORIGINS`

**Synopsis**

```sh signature
KINDGI_CORS_ORIGINS=<value>
```

Comma-separated browser origins allowed to call, cross-origin, the routes a public run token can use (`GET /v1/runs/{runId}/progress` and its stream). Exact origins, no wildcards. Unset: no CORS headers on any route.

**Values**

- **Read by:** the server
- **Required:** no
- **Default:** none: no CORS headers on any route

**Example**

```sh
KINDGI_CORS_ORIGINS=https://app.example.com
```

**See also**

- [Security](https://docs.kindgi.com/v0.1.8/concepts/security.md)
- [Follow a run from the browser](https://docs.kindgi.com/v0.1.8/guides/runs/follow-from-the-browser.md)

### `KINDGI_RUN_LEASE_MS`

**Synopsis**

```sh signature
KINDGI_RUN_LEASE_MS=<value>
```

How long a run's executor lease lasts without renewal, in milliseconds. Every unfinished run and every running eval run holds one, renewed every quarter of this by the server executing it. When a server stops without a shutdown (killed, out of memory, a crash), their leases run out, and a server's sweep fails them: a run sending `run.finished`, an eval run ending `failed`, interrupted, with its finished cases kept. Default 300000 (5 minutes); at least 10000, or the server refuses to start. A shorter lease finds such runs sooner, at the cost of more renewals.

**Values**

- **Read by:** the server
- **Required:** no
- **Default:** `300000` (5 minutes); at least `10000`

**Example**

```sh
KINDGI_RUN_LEASE_MS=300000
```

**See also**

- [Runs, the journal and durability](https://docs.kindgi.com/v0.1.8/concepts/runs.md)

### `KINDGI_RUN_SWEEP_INTERVAL_MS`

**Synopsis**

```sh signature
KINDGI_RUN_SWEEP_INTERVAL_MS=<value>
```

How often each server sweeps for runs and eval runs whose executor lease ran out, in milliseconds. A run or eval run whose server stopped without a shutdown is failed within about `KINDGI_RUN_LEASE_MS` plus this. Default 60000; at least 1000, and shorter than `KINDGI_RUN_LEASE_MS`, or the server refuses to start.

**Values**

- **Read by:** the server
- **Required:** no
- **Default:** `60000` (1 minute); at least `1000`, shorter than the lease

**Example**

```sh
KINDGI_RUN_SWEEP_INTERVAL_MS=60000
```

**See also**

- [Runs, the journal and durability](https://docs.kindgi.com/v0.1.8/concepts/runs.md)

### `KINDGI_RUN_ENDED_CHECK_MS`

**Synopsis**

```sh signature
KINDGI_RUN_ENDED_CHECK_MS=<value>
```

How often each server stops the runs it executes that were ended from outside (cancelled, on this server or another), in milliseconds. A run stops starting steps at its next write either way; this also stops a step that writes nothing for a while, such as a long model call, within this time of a cancel. Default 5000; at least 1000, or the server refuses to start.

**Values**

- **Read by:** the server
- **Required:** no
- **Default:** `5000` (5 seconds); at least `1000`

**Example**

```sh
KINDGI_RUN_ENDED_CHECK_MS=5000
```

**See also**

- [Operate a self-hosted runtime](https://docs.kindgi.com/v0.1.8/deploy/operate.md)

### `KINDGI_MEMORY_EMBEDDINGS`

**Synopsis**

```sh signature
KINDGI_MEMORY_EMBEDDINGS=<value>
```

Turns on memory search by meaning, with the embedding provider: `openai-compat`, an embeddings endpoint that speaks OpenAI's `POST /embeddings` (OpenAI, Ollama, vLLM, Hugging Face TEI, LM Studio; set `KINDGI_MEMORY_EMBEDDINGS_URL` and `KINDGI_MEMORY_EMBEDDINGS_MODEL`), or `local:<model>`, a model run inside the server (`bge-small-en-v1.5`, `nomic-embed-text-v1.5` or `mxbai-embed-large-v1`; only for a server run from source on macOS or glibc Linux: the runtime image can't load it, and the server refuses to start there). Facts are embedded when written, and a background job embeds the ones already there; agents' `semantic` and `both` retrieval and `/v1/memory/retrieve` then search by meaning. Unset (the default): keyword search only; a `semantic` intent fails its turn with `semantic-unavailable`, `both` runs its keyword half, and publishing such an agent warns. An endpoint that doesn't answer yet doesn't stop the server: it is retried in the background (its first answer gives the dimensions), search by meaning waits for it (`semantic-unavailable` meanwhile, writes kept without a vector until then), and `/ready` says `memoryEmbeddings: unavailable (retrying)`.

**Values**

- **Read by:** the server
- **Required:** no
- **Default:** none: memory search by keyword only

**Example**

```sh
KINDGI_MEMORY_EMBEDDINGS=openai-compat
```

**See also**

- [Operate a self-hosted runtime](https://docs.kindgi.com/v0.1.8/deploy/operate.md)

### `KINDGI_MEMORY_EMBEDDINGS_URL`

**Synopsis**

```sh signature
KINDGI_MEMORY_EMBEDDINGS_URL=<value>
```

With `KINDGI_MEMORY_EMBEDDINGS=openai-compat`: the endpoint's base URL, with its `/v1` (`http://localhost:11434/v1` for Ollama, `https://api.openai.com/v1`). Required then.

**Values**

- **Read by:** the server
- **Required:** no
- **Default:** none: required when `KINDGI_MEMORY_EMBEDDINGS=openai-compat`, not read otherwise

**Example**

```sh
KINDGI_MEMORY_EMBEDDINGS_URL=http://localhost:11434/v1
```

**See also**

- [Memory reference](https://docs.kindgi.com/v0.1.8/guides/agents/memory-reference.md)

### `KINDGI_MEMORY_EMBEDDINGS_MODEL`

**Synopsis**

```sh signature
KINDGI_MEMORY_EMBEDDINGS_MODEL=<value>
```

With `KINDGI_MEMORY_EMBEDDINGS=openai-compat`: the embedding model the endpoint serves (`nomic-embed-text`, `text-embedding-3-small`). Required then. A different model means a new vector space: the background job re-embeds every fact in it.

**Values**

- **Read by:** the server
- **Required:** no
- **Default:** none: required when `KINDGI_MEMORY_EMBEDDINGS=openai-compat`, not read otherwise

**Example**

```sh
KINDGI_MEMORY_EMBEDDINGS_MODEL=nomic-embed-text
```

**See also**

- [Memory reference](https://docs.kindgi.com/v0.1.8/guides/agents/memory-reference.md)

### `KINDGI_MEMORY_EMBEDDINGS_API_KEY`

**Synopsis**

```sh signature
KINDGI_MEMORY_EMBEDDINGS_API_KEY=<value>
```

With `KINDGI_MEMORY_EMBEDDINGS=openai-compat`: the endpoint's key, mapped into the environment from your secret store; Kindgi never stores it. Optional: Ollama and a local TEI take none.

**Values**

- **Read by:** the server
- **Required:** no
- **Default:** none: requests carry no key

**Example**

```sh
KINDGI_MEMORY_EMBEDDINGS_API_KEY=<API key of the embeddings endpoint>
```

**See also**

- [Memory reference](https://docs.kindgi.com/v0.1.8/guides/agents/memory-reference.md)

### `KINDGI_RETENTION_SWEEP_INTERVAL_MS`

**Synopsis**

```sh signature
KINDGI_RETENTION_SWEEP_INTERVAL_MS=<value>
```

How often the server purges deleted rows on its own, in milliseconds: in every tenant it serves, it purges for good the tombstones past their retention policy's grace, as `POST /v1/retention/sweep` does, holds (`graceSeconds: -1`) kept, and logs what it purged. Memory facts and conversations purge only under a policy naming their domain (`memory`, `conversation`), never under a `*` policy. Unset (the default): nothing purges on its own; sweep with `POST /v1/retention/sweep` or the console. At least 60000, or the server refuses to start.

**Values**

- **Read by:** the server
- **Required:** no
- **Default:** none: no automatic purge; at least `60000` when set

**Example**

```sh
KINDGI_RETENTION_SWEEP_INTERVAL_MS=3600000
```

**See also**

- [Keep and purge deleted data](https://docs.kindgi.com/v0.1.8/deploy/retention.md)

### `KINDGI_ERASURE_SHARED_WAIT_MS`

**Synopsis**

```sh signature
KINDGI_ERASURE_SHARED_WAIT_MS=<value>
```

How long an erasure waits for a turn of the person's that sits in a flow serving other people (cancelling it now could end their work), in milliseconds. The erasure shows `waiting-on-run`, naming the run, until then; at the deadline it cancels the run and goes on, and a tenant admin can stop the wait sooner (`kindgi memory erasures resume <id> --force`). Default 604800000 (7 days).

**Values**

- **Read by:** the server
- **Required:** no
- **Default:** `604800000` (7 days); at least `3600000`

**Example**

```sh
KINDGI_ERASURE_SHARED_WAIT_MS=604800000
```

**See also**

- [Erase a person's data](https://docs.kindgi.com/v0.1.8/guides/agents/erase-a-persons-data.md)

### `KINDGI_ERASURE_LEDGER_KEY_PATH`

**Synopsis**

```sh signature
KINDGI_ERASURE_LEDGER_KEY_PATH=<value>
```

Absolute path to the erasure ledger's 32-byte key file (mode 0600), the same on every replica, whatever the secrets backend. The ledger keeps a keyed hash of whom each erasure erased, so after a backup restore `kindgi memory erasures replay` finds them again. Unset (and no `KINDGI_ERASURE_LEDGER_KEY`): erasures still run, but can't be replayed after a restore. Losing the key means losing replay. This or `KINDGI_ERASURE_LEDGER_KEY`, not both.

**Values**

- **Read by:** the server
- **Required:** no
- **Default:** none (with `KINDGI_ERASURE_LEDGER_KEY` unset): erasures run but cannot be replayed after a restore

**Example**

```sh
KINDGI_ERASURE_LEDGER_KEY_PATH=/etc/kindgi/erasure-ledger.key
```

**See also**

- [Back up and restore the runtime's database](https://docs.kindgi.com/v0.1.8/deploy/back-up-and-restore.md)

### `KINDGI_ERASURE_LEDGER_KEY`

**Synopsis**

```sh signature
KINDGI_ERASURE_LEDGER_KEY=<value>
```

The erasure ledger's 32-byte key itself, base64: for platforms that give secrets as environment variables (Cloud Run with Secret Manager), where a key file's mode can't be 0600. See `KINDGI_ERASURE_LEDGER_KEY_PATH`; this or that, not both.

**Values**

- **Read by:** the server
- **Required:** no
- **Default:** none: same as `KINDGI_ERASURE_LEDGER_KEY_PATH`

**Example**

```sh
# 32 random bytes, base64
KINDGI_ERASURE_LEDGER_KEY="$(openssl rand -base64 32)"
```

**See also**

- [Back up and restore the runtime's database](https://docs.kindgi.com/v0.1.8/deploy/back-up-and-restore.md)
- [Self-host Kindgi](https://docs.kindgi.com/v0.1.8/deploy/self-host.md)

### `KINDGI_ERASURE_LEDGER_EXPORT`

**Synopsis**

```sh signature
KINDGI_ERASURE_LEDGER_EXPORT=<value>
```

Where the server keeps copies of the erasure ledger, off the database, so erasures can be replayed after a backup restore: `local:<absolute dir>` or `gcs:<bucket>[/<prefix>]` (Application Default Credentials; creating objects is all it needs). On an interval (`KINDGI_ERASURE_LEDGER_EXPORT_INTERVAL_MS`) it writes the ledger of every tenant it serves as one file (keyed hashes only, never whom an erasure names), as `kindgi memory erasures export` does for one tenant: a new file when the ledger changed, and at least one a day. It never overwrites or deletes one; keep them longer than your backups. Replay the newest after a restore with `kindgi-runtime erasures replay`. Unset (the default): the ledger stays in the database only.

**Values**

- **Read by:** the server
- **Required:** no
- **Default:** none: the ledger stays in the database only

**Example**

```sh
KINDGI_ERASURE_LEDGER_EXPORT=gcs:acme-erasure-ledger/runtime
```

**See also**

- [Back up and restore the runtime's database](https://docs.kindgi.com/v0.1.8/deploy/back-up-and-restore.md)

### `KINDGI_ERASURE_LEDGER_EXPORT_INTERVAL_MS`

**Synopsis**

```sh signature
KINDGI_ERASURE_LEDGER_EXPORT_INTERVAL_MS=<value>
```

How often the server writes the erasure ledger to `KINDGI_ERASURE_LEDGER_EXPORT`, in milliseconds. An erasure made after the last copy and after the restore point is in neither, so this bounds what a restore can miss. Default 3600000 (an hour). At least 60000, and needs `KINDGI_ERASURE_LEDGER_EXPORT`, or the server refuses to start.

**Values**

- **Read by:** the server
- **Required:** no
- **Default:** `3600000` (an hour); at least `60000`

**Example**

```sh
KINDGI_ERASURE_LEDGER_EXPORT_INTERVAL_MS=900000
```

**See also**

- [Back up and restore the runtime's database](https://docs.kindgi.com/v0.1.8/deploy/back-up-and-restore.md)

### `KINDGI_PASSIVE`

**Synopsis**

```sh signature
KINDGI_PASSIVE=<value>
```

`on` boots a server that does nothing on its own, for looking at a restored backup before anyone uses it: none of its background work starts (runs, schedules and event triggers, webhook deliveries, sending permission changes to OpenFGA, the sweeps, erasures and the erasure ledger export), and it sends no email. Requests are served as usual, and a run started through the API still runs (in the server that took the request), so keep a passive server where only you can reach it. The startup output and `/ready` say it's passive. Off by default.

**Values**

- **Read by:** the server
- **Required:** no
- **Default:** `off`

**Example**

```sh
KINDGI_PASSIVE=on
```

**See also**

- [Back up and restore the runtime's database](https://docs.kindgi.com/v0.1.8/deploy/back-up-and-restore.md)

### `KINDGI_PAGINATION_KEY_PATH`

**Synopsis**

```sh signature
KINDGI_PAGINATION_KEY_PATH=<value>
```

Absolute path to the 32-byte key (mode 0600) every list's page cursors are sealed with, so a cursor never shows the row it points after; the same on every instance. Unset (and no `KINDGI_PAGINATION_KEY`): a key made at boot and kept nowhere, so a cursor handed out before a restart answers 400 after it, and more than one instance behind one address needs the key set. Rotate it yearly (each cursor is sealed with a random nonce): the new key here, the old one in `KINDGI_PAGINATION_PREVIOUS_KEY_PATH` for at least a day. Its own key: never the erasure ledger's or the secrets AAD key. This or `KINDGI_PAGINATION_KEY`, not both.

**Values**

- **Read by:** the server
- **Required:** no
- **Default:** none (with `KINDGI_PAGINATION_KEY` unset): a random key made at start and kept nowhere

**Example**

```sh
KINDGI_PAGINATION_KEY_PATH=/etc/kindgi/pagination.key
```

**See also**

- [Self-host Kindgi](https://docs.kindgi.com/v0.1.8/deploy/self-host.md)
- [Operate a self-hosted runtime](https://docs.kindgi.com/v0.1.8/deploy/operate.md)

### `KINDGI_PAGINATION_KEY`

**Synopsis**

```sh signature
KINDGI_PAGINATION_KEY=<value>
```

The page-cursor key itself, base64: for platforms that give secrets as environment variables (Cloud Run with Secret Manager). See `KINDGI_PAGINATION_KEY_PATH`; this or that, not both.

**Values**

- **Read by:** the server
- **Required:** no
- **Default:** none: same as `KINDGI_PAGINATION_KEY_PATH`

**Example**

```sh
# 32 random bytes, base64
KINDGI_PAGINATION_KEY="$(openssl rand -base64 32)"
```

**See also**

- [Operate a self-hosted runtime](https://docs.kindgi.com/v0.1.8/deploy/operate.md)

### `KINDGI_PAGINATION_PREVIOUS_KEY_PATH`

**Synopsis**

```sh signature
KINDGI_PAGINATION_PREVIOUS_KEY_PATH=<value>
```

While the page-cursor key rotates, the key before it (a 32-byte file, mode 0600): it still opens the cursors it sealed. Keep it at least a day (a cursor's life) after rotating, then remove it. Needs `KINDGI_PAGINATION_KEY(_PATH)`, and must differ from it. This or `KINDGI_PAGINATION_PREVIOUS_KEY`, not both.

**Values**

- **Read by:** the server
- **Required:** no
- **Default:** none: no previous key

**Example**

```sh
KINDGI_PAGINATION_PREVIOUS_KEY_PATH=/etc/kindgi/pagination.previous.key
```

**See also**

- [Self-host Kindgi](https://docs.kindgi.com/v0.1.8/deploy/self-host.md)
- [Operate a self-hosted runtime](https://docs.kindgi.com/v0.1.8/deploy/operate.md)

### `KINDGI_PAGINATION_PREVIOUS_KEY`

**Synopsis**

```sh signature
KINDGI_PAGINATION_PREVIOUS_KEY=<value>
```

The previous page-cursor key itself, base64. See `KINDGI_PAGINATION_PREVIOUS_KEY_PATH`; this or that, not both.

**Values**

- **Read by:** the server
- **Required:** no
- **Default:** none: no previous key

**Example**

```sh
KINDGI_PAGINATION_PREVIOUS_KEY=<the KINDGI_PAGINATION_KEY value before the rotation>
```

**See also**

- [Operate a self-hosted runtime](https://docs.kindgi.com/v0.1.8/deploy/operate.md)

### `KINDGI_COMPLIANCE_CLASSIFIER`

**Synopsis**

```sh signature
KINDGI_COMPLIANCE_CLASSIFIER=<value>
```

Turns on the audit trail's compliance features: `shipped` uses the classifier the runtime ships; or give the absolute path of your own classifier JSON. When set, the server serves `/v1/compliance/*` (audit events as compliance evidence, and their signed export) and **purges audit events by kind, as the classifier says**. With `shipped`: authorization decisions after 90 days (denials after 365), run outcomes and guardrail violations after 730 days; secret changes and approval decisions are kept (legal hold), and so are kinds the classifier doesn't list. Unset (the default): no `/v1/compliance/*`, and no audit event is ever purged.

**Values**

- **Read by:** the server
- **Required:** no
- **Default:** none: no compliance routes, and no audit event is purged

**Example**

```sh
KINDGI_COMPLIANCE_CLASSIFIER=shipped
```

**See also**

- [Keep and purge deleted data](https://docs.kindgi.com/v0.1.8/deploy/retention.md)

### `KINDGI_ARTIFACTS`

**Synopsis**

```sh signature
KINDGI_ARTIFACTS=<value>
```

Where artifacts' files go, which turns on `/v1/artifacts`: `local:<absolute dir>` (a directory on this machine) or `gcs:<bucket>[/<prefix>]` (a Google Cloud Storage bucket, through Application Default Credentials: workload identity on GCP, no keys to store). Metadata is in Postgres; a deleted artifact is purged under the `artifact` retention policy. Unset (the default): no `/v1/artifacts`. `kindgi dev` sets it to the pack's `.kindgi/dev/artifacts`.

**Values**

- **Read by:** the server
- **Required:** no
- **Default:** none: artifacts are off (no `/v1/artifacts`)

**Example**

```sh
KINDGI_ARTIFACTS=gcs:acme-artifacts/prod
```

**See also**

- [Operate a self-hosted runtime](https://docs.kindgi.com/v0.1.8/deploy/operate.md)
- [Serve many tenants](https://docs.kindgi.com/v0.1.8/deploy/serve-many-tenants.md)
- [Keep files with a run](https://docs.kindgi.com/v0.1.8/guides/runs/keep-files-with-a-run.md)

### `KINDGI_ARTIFACT_MAX_BYTES`

**Synopsis**

```sh signature
KINDGI_ARTIFACT_MAX_BYTES=<value>
```

The most bytes one artifact upload may carry, the whole request body; more is `413 artifact-too-large`. Default 104857600 (100 MiB).

**Values**

- **Read by:** the server
- **Required:** no
- **Default:** `104857600` (100 MiB)

**Example**

```sh
KINDGI_ARTIFACT_MAX_BYTES=104857600
```

**See also**

- [Keep files with a run](https://docs.kindgi.com/v0.1.8/guides/runs/keep-files-with-a-run.md)

### `KINDGI_TURN_MEMORY_MIB`

**Synopsis**

```sh signature
KINDGI_TURN_MEMORY_MIB=<value>
```

Used from runtime 0.1.8; earlier runtimes ignore it. The most memory, in MiB, that every tenant's running agent turns may hold together. A turn is charged by size before each model call: (4 + the iterations it has done) × its conversation as sent, plus a resumed run's journal. Past the limit, the turn fails with `429 turn-memory-limit-reached` (`limit: server`). Unset: no limit. Kindgi Cloud sets 384.

**Values**

- **Read by:** the server
- **Required:** no
- **Default:** none: no limit on every tenant's turns together

**Example**

```sh
KINDGI_TURN_MEMORY_MIB=384
```

**See also**

- [Self-host Kindgi](https://docs.kindgi.com/v0.1.8/deploy/self-host.md)
- [Operate a self-hosted runtime](https://docs.kindgi.com/v0.1.8/deploy/operate.md)

### `KINDGI_TURN_MEMORY_PER_TENANT_MIB`

**Synopsis**

```sh signature
KINDGI_TURN_MEMORY_PER_TENANT_MIB=<value>
```

Used from runtime 0.1.8; earlier runtimes ignore it. The most memory, in MiB, that one tenant's running agent turns may hold, charged as for `KINDGI_TURN_MEMORY_MIB`. Past it, the turn fails with `429 turn-memory-limit-reached` (`limit: tenant`), and a resume whose journal alone is bigger fails at once. Unset: no limit per tenant. Kindgi Cloud sets 192.

**Values**

- **Read by:** the server
- **Required:** no
- **Default:** none: no limit per tenant

**Example**

```sh
KINDGI_TURN_MEMORY_PER_TENANT_MIB=192
```

**See also**

- [Self-host Kindgi](https://docs.kindgi.com/v0.1.8/deploy/self-host.md)
- [Operate a self-hosted runtime](https://docs.kindgi.com/v0.1.8/deploy/operate.md)

## Logging

### `KINDGI_LOG_LEVEL`

**Synopsis**

```sh signature
KINDGI_LOG_LEVEL=error|warn|info|debug|trace
```

The lowest level written: `error`, `warn`, `info` (default), `debug` or `trace`. At `info` an idle runtime writes nothing after its boot record but background work that did something. An unknown level stops the server at boot, naming this variable (exit code 2).

**Values**

- **Read by:** the server
- **Required:** no
- **Values:** `error`, `warn`, `info`, `debug`, `trace`
- **Default:** `info`

**Example**

```sh
KINDGI_LOG_LEVEL=info
```

**See also**

- [Logs](https://docs.kindgi.com/v0.1.8/deploy/logs.md)
- [Operate a self-hosted runtime](https://docs.kindgi.com/v0.1.8/deploy/operate.md)

### `KINDGI_LOG_LEVELS`

**Synopsis**

```sh signature
KINDGI_LOG_LEVELS=<value>
```

Levels per subsystem, over `KINDGI_LOG_LEVEL`: a comma list of `subsystem=level`, such as `kernel=debug,http=warn`. A dotted subsystem takes its parent's level (`kernel=debug` covers `kernel.sweeper`). A malformed entry stops the server at boot (exit code 2); a subsystem nothing logs under is a warning at boot.

**Values**

- **Read by:** the server
- **Required:** no
- **Default:** none: no per-subsystem levels; `KINDGI_LOG_LEVEL` applies everywhere

**Example**

```sh
KINDGI_LOG_LEVELS=kernel=debug
```

**See also**

- [Logs](https://docs.kindgi.com/v0.1.8/deploy/logs.md)
- [Operate a self-hosted runtime](https://docs.kindgi.com/v0.1.8/deploy/operate.md)

### `KINDGI_LOG_FORMAT`

**Synopsis**

```sh signature
KINDGI_LOG_FORMAT=auto|json|pretty
```

`json`: one record per line (`time`, `level`, `severity`, `subsystem`, `message`, then the ids), for a log collector. `pretty`: for a person at a terminal. `auto` (default): pretty when stdout is a terminal or `KINDGI_DEV=true`, JSON otherwise. `kindgi dev` sets `pretty`.

**Values**

- **Read by:** the server
- **Required:** no
- **Values:** `auto`, `json`, `pretty`
- **Default:** `auto`: `pretty` when stdout is a terminal or `KINDGI_DEV` is on, else `json`

**Example**

```sh
KINDGI_LOG_FORMAT=json
```

**See also**

- [Logs](https://docs.kindgi.com/v0.1.8/deploy/logs.md)
- [Operate a self-hosted runtime](https://docs.kindgi.com/v0.1.8/deploy/operate.md)

## Secrets backend selection

### `KINDGI_SECRETS_BACKEND`

**Synopsis**

```sh signature
KINDGI_SECRETS_BACKEND=none|postgres|dotenv|secret-manager
```

Where secret bytes live. `none` (default; /v1/secrets/\* unmounted) | `postgres` (envelope-encrypted, needs KMS) | `dotenv` (`.env` files in a directory; development only, needs `KINDGI_DEV=true`) | `secret-manager` (your own secret manager holds the bytes; pick it with `KINDGI_SECRETS_MANAGER`).

**Values**

- **Read by:** the server
- **Required:** no
- **Values:** `none`, `postgres`, `dotenv`, `secret-manager`
- **Default:** `none` (the secrets routes are not served)

**Example**

```sh
KINDGI_SECRETS_BACKEND=postgres
```

**See also**

- [Turn on sign-in](https://docs.kindgi.com/v0.1.8/deploy/sign-in.md)
- [Webhook events and deliveries](https://docs.kindgi.com/v0.1.8/guides/webhooks/webhook-events.md)

### `KINDGI_SECRETS_MANAGER`

**Synopsis**

```sh signature
KINDGI_SECRETS_MANAGER=azure|gcp|aws|vault
```

Which secret manager the `secret-manager` backend keeps secrets in: `azure` (Azure Key Vault, see `KINDGI_SECRETS_AZURE_VAULT_URL`), `gcp` (Google Secret Manager, in the project `KINDGI_SECRETS_GCP_PROJECT_ID` names; the server's service account needs roles/secretmanager.admin there), `vault` (HashiCorp Vault or OpenBao, see `KINDGI_SECRETS_VAULT_ADDR`) or `aws` (AWS Secrets Manager, used from runtime 0.1.7: in the region `KINDGI_SECRETS_AWS_REGION` names, signed in as the server's AWS identity, `KINDGI_AWS_IDENTITY`; runtime 0.1.6 refuses it at startup). Kindgi reads and writes them with the server's own identity and keeps only their names and version numbers in its database.

**Values**

- **Read by:** the server, with `KINDGI_SECRETS_BACKEND=secret-manager`
- **Required:** yes
- **Values:** `azure`, `gcp`, `aws`, `vault`
- **Default:** none (required)

**Example**

```sh
KINDGI_SECRETS_MANAGER=azure
```

**See also**

- [Keep secrets in your own secret manager](https://docs.kindgi.com/v0.1.8/deploy/secret-manager.md)
- [Secrets and environment](https://docs.kindgi.com/v0.1.8/guides/secrets.md)

### `KINDGI_SECRETS_DOTENV_DIR`

**Synopsis**

```sh signature
KINDGI_SECRETS_DOTENV_DIR=<value>
```

Absolute path of the directory whose `.env` files hold secrets (`dotenv` backend). Mount the project directory here; secrets written through the API land in its files.

**Values**

- **Read by:** the server, with `KINDGI_SECRETS_BACKEND=dotenv`
- **Required:** yes
- **Default:** none (required)

**Example**

```sh
KINDGI_SECRETS_DOTENV_DIR=/pack
```

**See also**

- [Keep secrets in your own secret manager](https://docs.kindgi.com/v0.1.8/deploy/secret-manager.md)
- [Secrets and environment](https://docs.kindgi.com/v0.1.8/guides/secrets.md)

### `KINDGI_SECRETS_DOTENV_FILES`

**Synopsis**

```sh signature
KINDGI_SECRETS_DOTENV_FILES=<value>
```

Comma-separated `.env` files read for secrets (`dotenv` backend), relative to `KINDGI_SECRETS_DOTENV_DIR`, lowest precedence first. Default `.env,.env.local`.

**Values**

- **Read by:** the server, with `KINDGI_SECRETS_BACKEND=dotenv`
- **Required:** no
- **Default:** `.env,.env.local`

**Example**

```sh
KINDGI_SECRETS_DOTENV_FILES=.env,.env.local
```

**See also**

- [Keep secrets in your own secret manager](https://docs.kindgi.com/v0.1.8/deploy/secret-manager.md)
- [Secrets and environment](https://docs.kindgi.com/v0.1.8/guides/secrets.md)

### `KINDGI_SECRETS_BACKEND_KMS`

**Synopsis**

```sh signature
KINDGI_SECRETS_BACKEND_KMS=gcp|azure|aws|libsodium
```

Which KMS wraps DEKs (postgres backend only): `gcp` (Google Cloud KMS), `azure` (an Azure Key Vault key, see `KINDGI_SECRETS_AZURE_KEY_ID`), or `libsodium`, a key this runtime holds (see `KINDGI_SECRETS_LOCAL_KEY_PATH`). Reserved: `aws`.

**Values**

- **Read by:** the server, with `KINDGI_SECRETS_BACKEND=postgres`
- **Required:** yes
- **Values:** `gcp`, `azure`, `aws`, `libsodium`
- **Default:** none (required)

**Example**

```sh
KINDGI_SECRETS_BACKEND_KMS=gcp
```

**See also**

- [Keep secrets in your own secret manager](https://docs.kindgi.com/v0.1.8/deploy/secret-manager.md)
- [Secrets and environment](https://docs.kindgi.com/v0.1.8/guides/secrets.md)

### `KINDGI_SECRETS_AAD_KEY_PATH`

**Synopsis**

```sh signature
KINDGI_SECRETS_AAD_KEY_PATH=<value>
```

Absolute path to a 32-byte AAD/HMAC key file (mode 0600). Same key across every replica; protects AEAD associated data over every secret row. The postgres backend needs this or `KINDGI_SECRETS_AAD_KEY`, not both.

**Values**

- **Read by:** the server, with `KINDGI_SECRETS_BACKEND=postgres`
- **Required:** no
- **Default:** none (with `KINDGI_SECRETS_AAD_KEY` unset): no key; the postgres backend refuses to start without one of the two

**Example**

```sh
KINDGI_SECRETS_AAD_KEY_PATH=/etc/kindgi/secrets-aad.key
```

**See also**

- [Self-host Kindgi](https://docs.kindgi.com/v0.1.8/deploy/self-host.md)

### `KINDGI_SECRETS_AAD_KEY`

**Synopsis**

```sh signature
KINDGI_SECRETS_AAD_KEY=<value>
```

The 32-byte AAD/HMAC key itself, base64: for platforms that give secrets as environment variables (Cloud Run with Secret Manager, Container Apps with Key Vault), where a key file's mode can't be 0600. Reference one fixed version of it, never the latest: a new version would make every stored secret unreadable. The postgres backend needs this or `KINDGI_SECRETS_AAD_KEY_PATH`, not both.

**Values**

- **Read by:** the server, with `KINDGI_SECRETS_BACKEND=postgres`
- **Required:** no
- **Default:** none: same as `KINDGI_SECRETS_AAD_KEY_PATH`

**Example**

```sh
# 32 random bytes, base64
KINDGI_SECRETS_AAD_KEY="$(openssl rand -base64 32)"
```

**See also**

- [Self-host Kindgi](https://docs.kindgi.com/v0.1.8/deploy/self-host.md)

## GCP vendor config (postgres + gcp KMS; secret-manager + gcp)

### `KINDGI_SECRETS_GCP_PROJECT_ID`

**Synopsis**

```sh signature
KINDGI_SECRETS_GCP_PROJECT_ID=<value>
```

GCP project id owning the KMS keyring + key (postgres backend, KMS `gcp`), or holding the secrets in its Secret Manager (`secret-manager` backend, manager `gcp`; the server's service account needs to create, add versions to and disable them, e.g. Secret Manager Admin, in a project used for Kindgi's secrets alone).

**Values**

- **Read by:** the server, with `KINDGI_SECRETS_BACKEND=postgres` or `secret-manager`
- **Required:** yes
- **Default:** none (required)

**Example**

```sh
KINDGI_SECRETS_GCP_PROJECT_ID=my-proj
```

**See also**

- [Deploy on Google Cloud Run](https://docs.kindgi.com/v0.1.8/deploy/cloud-run.md)
- [Keep secrets in your own secret manager](https://docs.kindgi.com/v0.1.8/deploy/secret-manager.md)

### `KINDGI_SECRETS_GCP_LOCATION_ID`

**Synopsis**

```sh signature
KINDGI_SECRETS_GCP_LOCATION_ID=<value>
```

KMS location, e.g. `us-central1`, `europe-west1`, `global`.

**Values**

- **Read by:** the server, with `KINDGI_SECRETS_BACKEND=postgres`, `KINDGI_SECRETS_BACKEND_KMS=gcp`
- **Required:** yes
- **Default:** none (required)

**Example**

```sh
KINDGI_SECRETS_GCP_LOCATION_ID=us-central1
```

**See also**

- [Deploy on Google Cloud Run](https://docs.kindgi.com/v0.1.8/deploy/cloud-run.md)
- [Keep secrets in your own secret manager](https://docs.kindgi.com/v0.1.8/deploy/secret-manager.md)

### `KINDGI_SECRETS_GCP_KEY_RING_ID`

**Synopsis**

```sh signature
KINDGI_SECRETS_GCP_KEY_RING_ID=<value>
```

KMS keyring id within the location.

**Values**

- **Read by:** the server, with `KINDGI_SECRETS_BACKEND=postgres`, `KINDGI_SECRETS_BACKEND_KMS=gcp`
- **Required:** yes
- **Default:** none (required)

**Example**

```sh
KINDGI_SECRETS_GCP_KEY_RING_ID=kindgi
```

**See also**

- [Deploy on Google Cloud Run](https://docs.kindgi.com/v0.1.8/deploy/cloud-run.md)
- [Keep secrets in your own secret manager](https://docs.kindgi.com/v0.1.8/deploy/secret-manager.md)

### `KINDGI_SECRETS_GCP_KEY_ID`

**Synopsis**

```sh signature
KINDGI_SECRETS_GCP_KEY_ID=<value>
```

CryptoKey id within the keyring (symmetric GOOGLE_SYMMETRIC_ENCRYPTION).

**Values**

- **Read by:** the server, with `KINDGI_SECRETS_BACKEND=postgres`, `KINDGI_SECRETS_BACKEND_KMS=gcp`
- **Required:** yes
- **Default:** none (required)

**Example**

```sh
KINDGI_SECRETS_GCP_KEY_ID=secrets-kek
```

**See also**

- [Deploy on Google Cloud Run](https://docs.kindgi.com/v0.1.8/deploy/cloud-run.md)
- [Keep secrets in your own secret manager](https://docs.kindgi.com/v0.1.8/deploy/secret-manager.md)

## AWS vendor config (the server's AWS identity; secret-manager + aws)

### `KINDGI_AWS_IDENTITY`

**Synopsis**

```sh signature
KINDGI_AWS_IDENTITY=container|instance|web-identity|profile
```

Used from runtime 0.1.7; runtime 0.1.6 ignores it. Where the server's AWS credentials come from, for the settings that sign in as it (such as the Bedrock adapter's `auth: aws-identity`). `container`: the task's or pod's role from the container credentials endpoint (ECS and Fargate, and EKS Pod Identity). `instance`: the EC2 instance profile, IMDSv2 only (in a container on EC2, raise the IMDS hop limit to 2, or use `container`). `web-identity`: EKS IRSA, from the projected token file and `AWS_ROLE_ARN`, which the platform sets. `profile`: development only (`KINDGI_DEV=true`), a named profile from `~/.aws` as `aws login` or SSO makes it (`KINDGI_AWS_PROFILE`). Unset: the server has no AWS identity, and a registration that needs one is refused. Under `KINDGI_TENANT_HOST_ACCESS=isolated`, model providers can't sign in as it either. Never the AWS SDK's default chain or keys in the environment; Kindgi keeps no key.

**Values**

- **Read by:** the server
- **Required:** no
- **Values:** `container`, `instance`, `web-identity`, `profile`
- **Default:** none: the server has no AWS identity; a registration that needs one is refused

**Example**

```sh
KINDGI_AWS_IDENTITY=container
```

**See also**

- [Keep secrets in your own secret manager](https://docs.kindgi.com/v0.1.8/deploy/secret-manager.md)
- [Models](https://docs.kindgi.com/v0.1.8/guides/models.md)

### `KINDGI_AWS_PROFILE`

**Synopsis**

```sh signature
KINDGI_AWS_PROFILE=<value>
```

Used from runtime 0.1.7; runtime 0.1.6 ignores it. With `KINDGI_AWS_IDENTITY=profile` (development only): the profile in `~/.aws/config` the server signs in as. Default: `default`.

**Values**

- **Read by:** the server
- **Required:** no
- **Default:** `default`

**Example**

```sh
KINDGI_AWS_PROFILE=kindgi-dev
```

**See also**

- [Keep secrets in your own secret manager](https://docs.kindgi.com/v0.1.8/deploy/secret-manager.md)

### `KINDGI_AWS_ROLE_ARN`

**Synopsis**

```sh signature
KINDGI_AWS_ROLE_ARN=<value>
```

Used from runtime 0.1.7; runtime 0.1.6 ignores it. An IAM role the server assumes on top of its AWS identity (STS, 1-hour sessions renewed before they expire): least privilege, or a role in another account. An IAM role's ARN, `arn:aws:iam::<account>:role/<name>`. Unset: the identity's own permissions.

**Values**

- **Read by:** the server
- **Required:** no
- **Default:** none: no role is assumed on top of the server's AWS identity

**Example**

```sh
KINDGI_AWS_ROLE_ARN=arn:aws:iam::123456789012:role/kindgi-bedrock
```

**See also**

- [Keep secrets in your own secret manager](https://docs.kindgi.com/v0.1.8/deploy/secret-manager.md)

### `KINDGI_AWS_ROLE_SESSION_NAME`

**Synopsis**

```sh signature
KINDGI_AWS_ROLE_SESSION_NAME=<value>
```

Used from runtime 0.1.7; runtime 0.1.6 ignores it. With `KINDGI_AWS_ROLE_ARN`: the role session's name, as CloudTrail shows it. Default: `kindgi-runtime`.

**Values**

- **Read by:** the server
- **Required:** no
- **Default:** `kindgi-runtime`

**Example**

```sh
KINDGI_AWS_ROLE_SESSION_NAME=kindgi-runtime
```

**See also**

- [Keep secrets in your own secret manager](https://docs.kindgi.com/v0.1.8/deploy/secret-manager.md)

### `KINDGI_AWS_STS_REGION`

**Synopsis**

```sh signature
KINDGI_AWS_STS_REGION=<value>
```

Used from runtime 0.1.7; runtime 0.1.6 ignores it. With `KINDGI_AWS_ROLE_ARN` or `KINDGI_AWS_IDENTITY=web-identity`: the region whose STS endpoint is used (regional STS, never the global endpoint). Default: `AWS_REGION`. Neither: the server refuses to start.

**Values**

- **Read by:** the server
- **Required:** no
- **Default:** the value of `AWS_REGION`; the server refuses to start when a role or web identity is used and neither is set

**Example**

```sh
KINDGI_AWS_STS_REGION=us-east-2
```

**See also**

- [Keep secrets in your own secret manager](https://docs.kindgi.com/v0.1.8/deploy/secret-manager.md)

### `KINDGI_SECRETS_AWS_REGION`

**Synopsis**

```sh signature
KINDGI_SECRETS_AWS_REGION=<value>
```

Used from runtime 0.1.7 (0.1.6 refuses `KINDGI_SECRETS_MANAGER=aws` at startup). The AWS region whose Secrets Manager holds the secrets set through Kindgi's API (`secret-manager` backend, manager `aws`). The server signs in as its AWS identity (`KINDGI_AWS_IDENTITY`), never with `AWS_*` keys or the SDK's default chain. That identity needs, on `arn:aws:secretsmanager:<region>:<account>:secret:kindgi/*` only: `secretsmanager:CreateSecret`, `TagResource`, `PutSecretValue`, `GetSecretValue`, `DescribeSecret` and `DeleteSecret`. Secrets are named `kindgi/<hash>`, with the scope in their tags.

**Values**

- **Read by:** the server, with `KINDGI_SECRETS_BACKEND=secret-manager`, `KINDGI_SECRETS_MANAGER=aws`
- **Required:** yes
- **Default:** none (required)

**Example**

```sh
KINDGI_SECRETS_AWS_REGION=ca-central-1
```

**See also**

- [Keep secrets in your own secret manager](https://docs.kindgi.com/v0.1.8/deploy/secret-manager.md)

### `KINDGI_SECRETS_AWS_KMS_KEY_ID`

**Synopsis**

```sh signature
KINDGI_SECRETS_AWS_KMS_KEY_ID=<value>
```

Used from runtime 0.1.7 (0.1.6 refuses `KINDGI_SECRETS_MANAGER=aws` at startup). The KMS key AWS Secrets Manager encrypts Kindgi's secrets with (`secret-manager` backend, manager `aws`): a key ARN, key id or `alias/…`. The server's AWS identity then needs `kms:GenerateDataKey` and `kms:Decrypt` on it. Unset: AWS's own `aws/secretsmanager` key.

**Values**

- **Read by:** the server, with `KINDGI_SECRETS_BACKEND=secret-manager`, `KINDGI_SECRETS_MANAGER=aws`
- **Required:** no
- **Default:** none: AWS's own `aws/secretsmanager` key

**Example**

```sh
KINDGI_SECRETS_AWS_KMS_KEY_ID=alias/kindgi-secrets
```

**See also**

- [Keep secrets in your own secret manager](https://docs.kindgi.com/v0.1.8/deploy/secret-manager.md)

## Azure vendor config (the server's managed identity; postgres + azure KMS; secret-manager + azure)

### `KINDGI_AZURE_CLIENT_ID`

**Synopsis**

```sh signature
KINDGI_AZURE_CLIENT_ID=<value>
```

Client id of the user-assigned managed identity the server signs in to Azure with, for its Azure settings (`KINDGI_SECRETS_BACKEND_KMS=azure`, `KINDGI_SECRETS_MANAGER=azure`, `KINDGI_IMAGE_REGISTRY_AUTH=azure`). Unset: the service's system-assigned identity. With `KINDGI_DEV=true` (a laptop), the Azure CLI's sign-in (`az login`) first, then the managed identity. Kindgi keeps no key or secret for it.

**Values**

- **Read by:** the server
- **Required:** no
- **Default:** none: the service's system-assigned managed identity

**Example**

```sh
KINDGI_AZURE_CLIENT_ID=11111111-2222-3333-4444-555555555555
```

**See also**

- [Renew the license key](https://docs.kindgi.com/v0.1.8/deploy/renew-the-license-key.md)
- [Keep secrets in your own secret manager](https://docs.kindgi.com/v0.1.8/deploy/secret-manager.md)
- [Export signed evidence](https://docs.kindgi.com/v0.1.8/guides/observability/export-signed-evidence.md)

### `KINDGI_SECRETS_AZURE_KEY_ID`

**Synopsis**

```sh signature
KINDGI_SECRETS_AZURE_KEY_ID=<value>
```

The Azure Key Vault key that wraps DEKs (postgres backend, KMS `azure`): its URL without a version, `https://<vault>.vault.azure.net/keys/<name>`. New secrets are wrapped with the key's current version and remember it, so rotating the key needs no rewrite. A URL with a version is refused. The server's identity needs wrap and unwrap on the key (the Key Vault Crypto Service Encryption User role).

**Values**

- **Read by:** the server, with `KINDGI_SECRETS_BACKEND=postgres`, `KINDGI_SECRETS_BACKEND_KMS=azure`
- **Required:** yes
- **Default:** none (required)

**Example**

```sh
KINDGI_SECRETS_AZURE_KEY_ID=https://my-vault.vault.azure.net/keys/kindgi-secrets
```

**See also**

- [Deploy on Azure Container Apps](https://docs.kindgi.com/v0.1.8/deploy/azure-container-apps.md)
- [Keep secrets in your own secret manager](https://docs.kindgi.com/v0.1.8/deploy/secret-manager.md)

### `KINDGI_SECRETS_AZURE_VAULT_URL`

**Synopsis**

```sh signature
KINDGI_SECRETS_AZURE_VAULT_URL=<value>
```

The Azure Key Vault that holds the secrets set through Kindgi's API (`secret-manager` backend, manager `azure`): its URL, `https://<vault>.vault.azure.net`. Use a vault for these alone, not the one your deployment's own secrets are in: the server's identity needs the Key Vault Secrets Officer role on it, to create, version, disable and recover secrets.

**Values**

- **Read by:** the server, with `KINDGI_SECRETS_BACKEND=secret-manager`, `KINDGI_SECRETS_MANAGER=azure`
- **Required:** yes
- **Default:** none (required)

**Example**

```sh
KINDGI_SECRETS_AZURE_VAULT_URL=https://my-kindgi-secrets.vault.azure.net
```

**See also**

- [Deploy on Azure Container Apps](https://docs.kindgi.com/v0.1.8/deploy/azure-container-apps.md)
- [Keep secrets in your own secret manager](https://docs.kindgi.com/v0.1.8/deploy/secret-manager.md)

## HashiCorp Vault / OpenBao (secret-manager + vault)

### `KINDGI_SECRETS_VAULT_ADDR`

**Synopsis**

```sh signature
KINDGI_SECRETS_VAULT_ADDR=<value>
```

The Vault (or OpenBao) that holds the secrets set through Kindgi's API (`secret-manager` backend, manager `vault`): its address. `https://`, or plain `http://` only to a Vault Agent on the same machine (`http://127.0.0.1:8100`) or in development. The server's token needs, on its kv version 2 mount (`KINDGI_SECRETS_VAULT_MOUNT`): `create`, `read`, `update` on `<mount>/data/*`; `read`, `delete`, `list` on `<mount>/metadata/*`; `update` on `<mount>/delete/*` and `<mount>/destroy/*`. A custom CA: `NODE_EXTRA_CA_CERTS`.

**Values**

- **Read by:** the server, with `KINDGI_SECRETS_BACKEND=secret-manager`, `KINDGI_SECRETS_MANAGER=vault`
- **Required:** yes
- **Default:** none (required)

**Example**

```sh
KINDGI_SECRETS_VAULT_ADDR=https://vault.example.com:8200
```

**See also**

- [Keep secrets in your own secret manager](https://docs.kindgi.com/v0.1.8/deploy/secret-manager.md)

### `KINDGI_SECRETS_VAULT_MOUNT`

**Synopsis**

```sh signature
KINDGI_SECRETS_VAULT_MOUNT=<value>
```

The kv version 2 mount Kindgi keeps its secrets in. Use one for these alone. Default `secret`.

**Values**

- **Read by:** the server, with `KINDGI_SECRETS_BACKEND=secret-manager`, `KINDGI_SECRETS_MANAGER=vault`
- **Required:** no
- **Default:** `secret`

**Example**

```sh
KINDGI_SECRETS_VAULT_MOUNT=kindgi
```

**See also**

- [Keep secrets in your own secret manager](https://docs.kindgi.com/v0.1.8/deploy/secret-manager.md)

### `KINDGI_SECRETS_VAULT_NAMESPACE`

**Synopsis**

```sh signature
KINDGI_SECRETS_VAULT_NAMESPACE=<value>
```

The Vault Enterprise or HCP Vault namespace (`admin`, `admin/team`). Unset: none.

**Values**

- **Read by:** the server, with `KINDGI_SECRETS_BACKEND=secret-manager`, `KINDGI_SECRETS_MANAGER=vault`
- **Required:** no
- **Default:** none: no namespace

**Example**

```sh
KINDGI_SECRETS_VAULT_NAMESPACE=admin
```

**See also**

- [Keep secrets in your own secret manager](https://docs.kindgi.com/v0.1.8/deploy/secret-manager.md)

### `KINDGI_SECRETS_VAULT_TOKEN_FILE`

**Synopsis**

```sh signature
KINDGI_SECRETS_VAULT_TOKEN_FILE=<value>
```

A file holding the server's Vault token, kept fresh by the platform: Vault Agent's sink, or a mounted secret. Read again every minute, and when Vault refuses the token. Set this or `KINDGI_SECRETS_VAULT_K8S_ROLE`. Kindgi never reads `VAULT_TOKEN` or `~/.vault-token`.

**Values**

- **Read by:** the server, with `KINDGI_SECRETS_BACKEND=secret-manager`, `KINDGI_SECRETS_MANAGER=vault`
- **Required:** no
- **Default:** none: the server signs in with `KINDGI_SECRETS_VAULT_K8S_ROLE` instead; one of the two is required

**Example**

```sh
KINDGI_SECRETS_VAULT_TOKEN_FILE=/vault/secrets/token
```

**See also**

- [Keep secrets in your own secret manager](https://docs.kindgi.com/v0.1.8/deploy/secret-manager.md)

### `KINDGI_SECRETS_VAULT_K8S_ROLE`

**Synopsis**

```sh signature
KINDGI_SECRETS_VAULT_K8S_ROLE=<value>
```

The role of Vault's Kubernetes auth method the server logs in with, using its pod's service account token. Its token is renewed by logging in again before its lease ends. Set this or `KINDGI_SECRETS_VAULT_TOKEN_FILE`.

**Values**

- **Read by:** the server, with `KINDGI_SECRETS_BACKEND=secret-manager`, `KINDGI_SECRETS_MANAGER=vault`
- **Required:** no
- **Default:** none: the server signs in with `KINDGI_SECRETS_VAULT_TOKEN_FILE` instead; one of the two is required

**Example**

```sh
KINDGI_SECRETS_VAULT_K8S_ROLE=kindgi-runtime
```

**See also**

- [Keep secrets in your own secret manager](https://docs.kindgi.com/v0.1.8/deploy/secret-manager.md)

### `KINDGI_SECRETS_VAULT_K8S_MOUNT`

**Synopsis**

```sh signature
KINDGI_SECRETS_VAULT_K8S_MOUNT=<value>
```

Where Vault's Kubernetes auth method is mounted, with `KINDGI_SECRETS_VAULT_K8S_ROLE`. Default `kubernetes`.

**Values**

- **Read by:** the server, with `KINDGI_SECRETS_BACKEND=secret-manager`, `KINDGI_SECRETS_MANAGER=vault`
- **Required:** no
- **Default:** `kubernetes`

**Example**

```sh
KINDGI_SECRETS_VAULT_K8S_MOUNT=kubernetes-prod
```

**See also**

- [Keep secrets in your own secret manager](https://docs.kindgi.com/v0.1.8/deploy/secret-manager.md)

### `KINDGI_SECRETS_VAULT_K8S_TOKEN_FILE`

**Synopsis**

```sh signature
KINDGI_SECRETS_VAULT_K8S_TOKEN_FILE=<value>
```

The service account token the Kubernetes login sends, with `KINDGI_SECRETS_VAULT_K8S_ROLE`. Default `/var/run/secrets/kubernetes.io/serviceaccount/token`; set it for a projected token with its own audience.

**Values**

- **Read by:** the server, with `KINDGI_SECRETS_BACKEND=secret-manager`, `KINDGI_SECRETS_MANAGER=vault`
- **Required:** no
- **Default:** `/var/run/secrets/kubernetes.io/serviceaccount/token`

**Example**

```sh
KINDGI_SECRETS_VAULT_K8S_TOKEN_FILE=/var/run/secrets/vault/token
```

**See also**

- [Keep secrets in your own secret manager](https://docs.kindgi.com/v0.1.8/deploy/secret-manager.md)

## Local key (postgres + libsodium: a key this runtime holds, single-node)

### `KINDGI_SECRETS_LOCAL_KEY_PATH`

**Synopsis**

```sh signature
KINDGI_SECRETS_LOCAL_KEY_PATH=<value>
```

Absolute path to the local key: a file of 32 random bytes (`openssl rand 32`, mode 0600), for `KINDGI_SECRETS_BACKEND_KMS=libsodium`. It wraps every secret's data key, and it's held on this host, next to the database: back it up apart from the database, since a lost key loses every secret. Replicas mount the same file. Starting with a different key than the secrets were stored under is refused. This or `KINDGI_SECRETS_LOCAL_KEY`, not both.

**Values**

- **Read by:** the server, with `KINDGI_SECRETS_BACKEND=postgres`, `KINDGI_SECRETS_BACKEND_KMS=libsodium`
- **Required:** no
- **Default:** none (with `KINDGI_SECRETS_LOCAL_KEY` unset): no key; the libsodium KMS refuses to start without one of the two

**Example**

```sh
KINDGI_SECRETS_LOCAL_KEY_PATH=/etc/kindgi/secrets-local.key
```

**See also**

- [Self-host Kindgi](https://docs.kindgi.com/v0.1.8/deploy/self-host.md)

### `KINDGI_SECRETS_LOCAL_KEY`

**Synopsis**

```sh signature
KINDGI_SECRETS_LOCAL_KEY=<value>
```

The local key itself, base64 (32 bytes; `openssl rand -base64 32`): for platforms that give secrets as environment variables. This or `KINDGI_SECRETS_LOCAL_KEY_PATH`, not both.

**Values**

- **Read by:** the server, with `KINDGI_SECRETS_BACKEND=postgres`, `KINDGI_SECRETS_BACKEND_KMS=libsodium`
- **Required:** no
- **Default:** none: same as `KINDGI_SECRETS_LOCAL_KEY_PATH`

**Example**

```sh
# 32 random bytes, base64
KINDGI_SECRETS_LOCAL_KEY="$(openssl rand -base64 32)"
```

**See also**

- [Self-host Kindgi](https://docs.kindgi.com/v0.1.8/deploy/self-host.md)

### `KINDGI_SECRETS_LOCAL_KEY_ACK`

**Synopsis**

```sh signature
KINDGI_SECRETS_LOCAL_KEY_ACK=single-node
```

Exactly `single-node`: the operator's acknowledgement that the secrets' key is held on this host, next to the database, not in a cloud KMS. Not FIPS 140-3; for regulated workloads, use a cloud KMS. Required with `KINDGI_SECRETS_BACKEND_KMS=libsodium`.

**Values**

- **Read by:** the server, with `KINDGI_SECRETS_BACKEND=postgres`, `KINDGI_SECRETS_BACKEND_KMS=libsodium`
- **Required:** yes
- **Values:** `single-node`
- **Default:** none (required)

**Example**

```sh
KINDGI_SECRETS_LOCAL_KEY_ACK=single-node
```

**See also**

- [Self-host Kindgi](https://docs.kindgi.com/v0.1.8/deploy/self-host.md)

## Pack service (runs the pack code: tools and guardrail checks)

### `KINDGI_PACK_SERVICE_URL`

**Synopsis**

```sh signature
KINDGI_PACK_SERVICE_URL=<value>
```

Base URL of the pack service, the separate process that runs the pack code (its tools and guardrail checks), e.g. `http://pack-service:8080` or its internal Cloud Run URL. Set: the server calls it over HTTP for every pack tool and check, and needs `KINDGI_PACK_SERVICE_TOKEN`. Unset: no pack code runs, and a pack tool fails saying no pack service is wired. `kindgi dev` runs its own.

**Values**

- **Read by:** the server
- **Required:** no
- **Default:** none: no pack service; a pack tool fails saying none is wired

**Example**

```sh
KINDGI_PACK_SERVICE_URL=http://pack-service:8080
```

**See also**

- [Deploy on Google Cloud Run](https://docs.kindgi.com/v0.1.8/deploy/cloud-run.md)
- [Self-host Kindgi](https://docs.kindgi.com/v0.1.8/deploy/self-host.md)
- [Serve many tenants](https://docs.kindgi.com/v0.1.8/deploy/serve-many-tenants.md)

### `KINDGI_PACK_CALL_TIMEOUT_MS`

**Synopsis**

```sh signature
KINDGI_PACK_CALL_TIMEOUT_MS=<value>
```

How long one pack call (a tool or a guardrail check) may take, in milliseconds. Default 120000. The deadline travels with the call, and the pack service aborts the handler when it passes.

**Values**

- **Read by:** the server, when it calls a pack service (`KINDGI_PACK_SERVICE_URL` set)
- **Required:** no
- **Default:** `120000` (2 minutes)

**Example**

```sh
KINDGI_PACK_CALL_TIMEOUT_MS=120000
```

**See also**

- [Self-host Kindgi](https://docs.kindgi.com/v0.1.8/deploy/self-host.md)
- [How Kindgi fits your app](https://docs.kindgi.com/v0.1.8/concepts/architecture.md)

### `KINDGI_PACK_SERVICE_AUTH`

**Synopsis**

```sh signature
KINDGI_PACK_SERVICE_AUTH=token|google-id-token
```

How the server proves itself to the pack service besides the pack token. `token` (default): the pack token alone. `google-id-token`: also a Google ID token for the pack service URL, from the server's own identity, for a pack service on Cloud Run behind IAM (`--no-allow-unauthenticated`); the URL must be https. The identity is a service account: on Cloud Run the server's own; elsewhere, impersonate one (a person's own credentials can't mint an ID token for a service).

**Values**

- **Read by:** the server, when it calls a pack service (`KINDGI_PACK_SERVICE_URL` set)
- **Required:** no
- **Values:** `token`, `google-id-token`
- **Default:** `token`

**Example**

```sh
KINDGI_PACK_SERVICE_AUTH=token
```

**See also**

- [Deploy on Google Cloud Run](https://docs.kindgi.com/v0.1.8/deploy/cloud-run.md)
- [Serve many tenants](https://docs.kindgi.com/v0.1.8/deploy/serve-many-tenants.md)

### `KINDGI_PACK_SERVICE_TOKEN`

**Synopsis**

```sh signature
KINDGI_PACK_SERVICE_TOKEN=<value>
```

Shared secret between the server and the pack service. The pack service refuses any call without it, and the server sends it with every call. Required by the pack service, and by the server when `KINDGI_PACK_SERVICE_URL` is set. Use a long random value (`openssl rand -base64 32`). Both sides drop surrounding whitespace (such as the trailing newline of a stored secret), and refuse whitespace or control characters inside: the token travels in an HTTP header. The pack service removes it from its environment before it loads the pack code.

**Values**

- **Read by:** the server, when it calls a pack service (`KINDGI_PACK_SERVICE_URL` set); the pack service
- **Required:** yes
- **Default:** none (required)

**Example**

```sh
# long random value, printable ASCII without spaces
KINDGI_PACK_SERVICE_TOKEN="$(openssl rand -hex 32)"
```

**See also**

- [Deploy on Google Cloud Run](https://docs.kindgi.com/v0.1.8/deploy/cloud-run.md)
- [Rotate the runtime's tokens and keys](https://docs.kindgi.com/v0.1.8/deploy/rotate-keys.md)
- [Self-host Kindgi](https://docs.kindgi.com/v0.1.8/deploy/self-host.md)
- [Serve many tenants](https://docs.kindgi.com/v0.1.8/deploy/serve-many-tenants.md)

### `KINDGI_PACK_INDEX`

**Synopsis**

```sh signature
KINDGI_PACK_INDEX=<value>
```

Path of the pack's `index.json` in the pack service's image. Default `/app/index.json`, where `kindgi build` puts it; the `--index` flag overrides it.

**Values**

- **Read by:** the pack service
- **Required:** no
- **Default:** `/app/index.json` (the `--index` flag wins)

**Example**

```sh
KINDGI_PACK_INDEX=/app/index.json
```

**See also**

- [Self-host Kindgi](https://docs.kindgi.com/v0.1.8/deploy/self-host.md)
- [How Kindgi fits your app](https://docs.kindgi.com/v0.1.8/concepts/architecture.md)

### `KINDGI_PACK_SERVICE_MAX_CONCURRENCY`

**Synopsis**

```sh signature
KINDGI_PACK_SERVICE_MAX_CONCURRENCY=<value>
```

How many calls the pack service runs at once. Past it, calls get 503 (`overloaded`), which the server retries. Default 32. On Cloud Run, set the service's concurrency to the same number.

**Values**

- **Read by:** the pack service
- **Required:** no
- **Default:** `32`

**Example**

```sh
KINDGI_PACK_SERVICE_MAX_CONCURRENCY=32
```

**See also**

- [Self-host Kindgi](https://docs.kindgi.com/v0.1.8/deploy/self-host.md)
- [How Kindgi fits your app](https://docs.kindgi.com/v0.1.8/concepts/architecture.md)

### `KINDGI_PACK_ENV_CHECK`

**Synopsis**

```sh signature
KINDGI_PACK_ENV_CHECK=strict|warn
```

What the pack service does when a name the pack's `env.required` declares is unset or empty in its environment. `strict` (default): it isn't ready, and `/readyz` and every call answer 503 naming the missing names. `warn`: it serves, and names them in its log and `/v1/info`. `kindgi dev` uses `warn`.

**Values**

- **Read by:** the pack service
- **Required:** no
- **Values:** `strict`, `warn`
- **Default:** `strict`

**Example**

```sh
KINDGI_PACK_ENV_CHECK=strict
```

**See also**

- [Self-host Kindgi](https://docs.kindgi.com/v0.1.8/deploy/self-host.md)
- [How Kindgi fits your app](https://docs.kindgi.com/v0.1.8/concepts/architecture.md)

### `KINDGI_PACK_ENV_FILTER`

**Synopsis**

```sh signature
KINDGI_PACK_ENV_FILTER=on|off
```

Whether the pack service keeps only the names its pack declares (`env.required`, `env.optional`). `on` (default): before the pack's code loads, it drops every other variable from its environment but `KINDGI_*` and the platform's own (the process's basics, the language runtime's settings, `PORT`, proxies and certificates, and Cloud Run's, AWS's and Azure's workload identity and metadata), and logs the dropped names, never their values (`WARN env-dropped`). `off`: every variable reaches the pack's code. `kindgi dev` uses `off`.

**Values**

- **Read by:** the pack service
- **Required:** no
- **Values:** `on`, `off`
- **Default:** `on`

**Example**

```sh
KINDGI_PACK_ENV_FILTER=on
```

**See also**

- [Self-host Kindgi](https://docs.kindgi.com/v0.1.8/deploy/self-host.md)
- [How Kindgi fits your app](https://docs.kindgi.com/v0.1.8/concepts/architecture.md)

### `KINDGI_PACK_ENV_DECLARED`

**Synopsis**

```sh signature
KINDGI_PACK_ENV_DECLARED=<value>
```

A Java or Scala pack service's declared names, comma-separated: the ones its launcher (`kindgi-pack-java`) keeps when `KINDGI_PACK_ENV_FILTER` is on, since a JVM can't drop a variable from its own environment. `kindgi build` sets it in the image from the pack's index; the service won't start when it differs from the index's `env`. Without it, the launcher drops nothing, and the service won't start while a name the pack doesn't declare reaches it.

**Values**

- **Read by:** the pack service
- **Required:** no
- **Default:** none: the launcher drops nothing (an image built by `kindgi build` sets it)

**Example**

```sh
KINDGI_PACK_ENV_DECLARED=DATABASE_URL,CACHE_DIR
```

**See also**

- [Self-host Kindgi](https://docs.kindgi.com/v0.1.8/deploy/self-host.md)
- [How Kindgi fits your app](https://docs.kindgi.com/v0.1.8/concepts/architecture.md)

### `KINDGI_PACK_TENANT_ID`

**Synopsis**

```sh signature
KINDGI_PACK_TENANT_ID=<value>
```

The one tenant this pack service serves: the tenant's id, a UUID, checked at startup. It needs a runtime that sends the `kindgi-tenant-id` header on its pack calls, 0.1.8 or later: with an earlier runtime, every call is refused. Set: the service refuses, with 421 (`pack-tenant-mismatch`), a call to `/v1/invoke` or `/v1/info` whose `kindgi-tenant-id` header is missing or names another tenant, and a tool call whose context names another tenant, before any pack code runs. The health probes stay open. The server sends the header on every call, from the deployment it routed the call to. An orchestrator that runs a pack service per tenant sets it. Unset: no check. It's a backstop for a misrouted call to an honest pack, not a boundary: the server's routing is.

**Values**

- **Read by:** the pack service
- **Required:** no
- **Default:** none: no check, the pack service answers any tenant's calls

**Example**

```sh
KINDGI_PACK_TENANT_ID=<UUID of the tenant this pack service serves>
```

**See also**

- [Self-host Kindgi](https://docs.kindgi.com/v0.1.8/deploy/self-host.md)
- [How Kindgi fits your app](https://docs.kindgi.com/v0.1.8/concepts/architecture.md)

### `KINDGI_PACK_ROUTING`

**Synopsis**

```sh signature
KINDGI_PACK_ROUTING=single|per-deployment
```

Used from runtime 0.1.8; earlier runtimes ignore it. How the server finds the pack service for a call. `single` (default): one pack service, `KINDGI_PACK_SERVICE_URL`, for every call. `per-deployment` needs `KINDGI_TENANCY=multi`: the endpoints are recorded through the system API, which a runtime serving one tenant does not have, so the server refuses to start with it there; `multi` starts only with it. Each call then goes to the newest deployment that shipped the tool or check, at the endpoint recorded for it by whatever runs the services (an orchestrator, or a script) with a `system:deployments` key. Each endpoint carries its own token and auth, so `KINDGI_PACK_SERVICE_URL`, `KINDGI_PACK_SERVICE_TOKEN` and `KINDGI_PACK_SERVICE_AUTH` are refused; `KINDGI_PACK_CALL_TIMEOUT_MS` still applies. A call whose deployment has no endpoint yet fails (`pack-endpoint-pending`): it never falls back to an older deployment or another service. Deployments registered before it was turned on have no routes; deploy them again.

**Values**

- **Read by:** the server
- **Required:** no
- **Values:** `single`, `per-deployment`
- **Default:** `single`: one pack service, `KINDGI_PACK_SERVICE_URL`, for every call

**Example**

```sh
KINDGI_PACK_ROUTING=single
```

**See also**

- [Self-host Kindgi](https://docs.kindgi.com/v0.1.8/deploy/self-host.md)
- [How Kindgi fits your app](https://docs.kindgi.com/v0.1.8/concepts/architecture.md)

### `KINDGI_PACK_ENDPOINT_HOSTS`

**Synopsis**

```sh signature
KINDGI_PACK_ENDPOINT_HOSTS=<value>
```

Used from runtime 0.1.8; earlier runtimes ignore it. With `KINDGI_PACK_ROUTING=per-deployment`: the hosts a deployment's pack endpoint may name, comma-separated. Each is a host name; `*.` and a domain, which matches one more label in front of it; or `*-`, a tail and a domain, which matches one more label ending with `-` and the tail. On Cloud Run, name each pack project with `*-<project number>.<region>.run.app`: Cloud Run URLs are `<service>-<project number>.<region>.run.app` for every project, so `*.`, or a `*-` tail that isn't a project number, with `run.app` or a domain under it would admit anyone's service, and the server refuses to start with one. Lowercase, with no scheme, port or path. Without it, the server refuses to start: per-deployment routing needs `KINDGI_TENANCY=multi`, which starts only with the hosts named. An endpoint's host must also begin with its deployment's own service name (`kp-` and 20 characters, then `-` or `.`), so an endpoint can't point one tenant's deployment at another's service.

**Values**

- **Read by:** the server
- **Required:** no
- **Default:** none: with `KINDGI_PACK_ROUTING=per-deployment`, the server refuses to start

**Example**

```sh
KINDGI_PACK_ENDPOINT_HOSTS=*-123456789012.us-central1.run.app
```

**See also**

- [Deploy on Google Cloud Run](https://docs.kindgi.com/v0.1.8/deploy/cloud-run.md)
- [Serve many tenants](https://docs.kindgi.com/v0.1.8/deploy/serve-many-tenants.md)

## Image registry (where deployments' images are read from)

### `KINDGI_IMAGE_REGISTRY_HOST`

**Synopsis**

```sh signature
KINDGI_IMAGE_REGISTRY_HOST=<value>
```

The registry host the credentials below are for, with its port when it has one (e.g. `registry.example`, `ghcr.io`). Other registries are read anonymously.

**Values**

- **Read by:** the server
- **Required:** no
- **Default:** none: every registry is read anonymously

**Example**

```sh
KINDGI_IMAGE_REGISTRY_HOST=registry.example
```

**See also**

- [Deploy on Azure Container Apps](https://docs.kindgi.com/v0.1.8/deploy/azure-container-apps.md)
- [Self-host Kindgi](https://docs.kindgi.com/v0.1.8/deploy/self-host.md)

### `KINDGI_IMAGE_REGISTRY_USERNAME`

**Synopsis**

```sh signature
KINDGI_IMAGE_REGISTRY_USERNAME=<value>
```

Username for `KINDGI_IMAGE_REGISTRY_HOST`. Used for the registry's token flow, or as basic auth.

**Values**

- **Read by:** the server
- **Required:** no
- **Default:** none: no credentials (anonymous reads)

**Example**

```sh
KINDGI_IMAGE_REGISTRY_USERNAME=kindgi-reader
```

**See also**

- [Self-host Kindgi](https://docs.kindgi.com/v0.1.8/deploy/self-host.md)

### `KINDGI_IMAGE_REGISTRY_PASSWORD`

**Synopsis**

```sh signature
KINDGI_IMAGE_REGISTRY_PASSWORD=<value>
```

Password or access token for `KINDGI_IMAGE_REGISTRY_HOST`. Read-only (pull) access is enough.

**Values**

- **Read by:** the server
- **Required:** no
- **Default:** none: no credentials (anonymous reads)

**Example**

```sh
KINDGI_IMAGE_REGISTRY_PASSWORD=<registry password or read-only access token>
```

**See also**

- [Self-host Kindgi](https://docs.kindgi.com/v0.1.8/deploy/self-host.md)

### `KINDGI_IMAGE_REGISTRY_INSECURE_HOSTS`

**Synopsis**

```sh signature
KINDGI_IMAGE_REGISTRY_INSECURE_HOSTS=<value>
```

Comma-separated registry hosts reached over plain HTTP instead of HTTPS, e.g. a local `registry:2`. Loopback hosts (`localhost`, `127.0.0.1`) always are.

**Values**

- **Read by:** the server
- **Required:** no
- **Default:** none: loopback hosts always use plain HTTP

**Example**

```sh
KINDGI_IMAGE_REGISTRY_INSECURE_HOSTS=registry.internal:5000
```

**See also**

- [Self-host Kindgi](https://docs.kindgi.com/v0.1.8/deploy/self-host.md)
- [Security](https://docs.kindgi.com/v0.1.8/concepts/security.md)

### `KINDGI_IMAGE_REGISTRY_AUTH`

**Synopsis**

```sh signature
KINDGI_IMAGE_REGISTRY_AUTH=static|google|azure
```

How the server signs in to `KINDGI_IMAGE_REGISTRY_HOST`. `static` (default): `KINDGI_IMAGE_REGISTRY_USERNAME` and `_PASSWORD`. `google`: the server's own Google identity (Application Default Credentials: the service's identity on Cloud Run), for Artifact Registry; no username or password is set, and Kindgi keeps no key file. `azure`: the server's own managed identity (see `KINDGI_AZURE_CLIENT_ID`), for Azure Container Registry, where it needs the AcrPull role; no username or password is set either.

**Values**

- **Read by:** the server
- **Required:** no
- **Values:** `static`, `google`, `azure`
- **Default:** `static`

**Example**

```sh
KINDGI_IMAGE_REGISTRY_AUTH=static
```

**See also**

- [Self-host Kindgi](https://docs.kindgi.com/v0.1.8/deploy/self-host.md)
- [Security](https://docs.kindgi.com/v0.1.8/concepts/security.md)

## Development (`kindgi dev`; each needs `KINDGI_DEV=true`)

### `KINDGI_PACK_DIR`

**Synopsis**

```sh signature
KINDGI_PACK_DIR=<value>
```

Development only: the pack directory `kindgi dev` runs. The server reads the pack's tools, guardrails, agents and flows from the index `kindgi dev` writes there (`.kindgi/dev/index.json`), on every change, instead of from Postgres; signed deployments are off. Mount the directory, not the file: the index is replaced by rename.

**Values**

- **Read by:** the server
- **Required:** no
- **Default:** none: packs come from signed deployments in Postgres

**Example**

```sh
KINDGI_PACK_DIR=/pack
```

**See also**

- [Serve many tenants](https://docs.kindgi.com/v0.1.8/deploy/serve-many-tenants.md)

### `KINDGI_DEV_CONSOLE_LOGIN`

**Synopsis**

```sh signature
KINDGI_DEV_CONSOLE_LOGIN=true|false|1|0
```

Development only: `true` lets the console log in by itself. `GET /__dev/bearer` hands it the API token, answering only requests addressed to a loopback host (`localhost`, `127.0.0.1`, `[::1]`). Needs the console.

**Values**

- **Read by:** the server
- **Required:** no
- **Values:** `true`, `false`, `1`, `0`
- **Default:** `false`

**Example**

```sh
KINDGI_DEV_CONSOLE_LOGIN=true
```

**See also**

- [Serve many tenants](https://docs.kindgi.com/v0.1.8/deploy/serve-many-tenants.md)

### `KINDGI_DEV_HOST_ALIAS`

**Synopsis**

```sh signature
KINDGI_DEV_HOST_ALIAS=<value>
```

Development only: where the server's outbound calls to a loopback address (`localhost`, `127.0.0.1`, `[::1]`) connect instead. This covers webhooks to the app, HTTP MCP endpoints, model providers and the pack service. Inside a container, loopback is the container itself; `kindgi dev` sets `host.docker.internal` on Docker Desktop. The URL, the `Host` header and the TLS server name stay as written; only the connection goes to the alias.

**Values**

- **Read by:** the server
- **Required:** no
- **Default:** none: calls to loopback addresses connect as written

**Example**

```sh
KINDGI_DEV_HOST_ALIAS=host.docker.internal
```

**See also**

- [Keep your tools' code away from your keys](https://docs.kindgi.com/v0.1.8/guides/secrets/dev-sandbox.md)
- [Self-host Kindgi](https://docs.kindgi.com/v0.1.8/deploy/self-host.md)
