Skip to content

Install with kindo-cli

Install the CLI, generate your configuration, and check your infrastructure before deploying Self-Managed Kindo (SMK) to your prepared Kubernetes cluster.

The Kindo CLI is distributed as an OCI artifact at oci://registry.kindo.ai/kindo-cli/kindo-cli, pulled with helm using the same registry.kindo.ai credentials as the rest of the install. It contains the CLI wheel (kindo_cli-*.whl) and a SHA256SUMS file.

ToolRequiredVersionPurpose
kubectlYes1.32+Cluster access
helmYesv4+Chart deployment
helmfileYesv1.2+Release orchestration
yqYesv4+YAML processing
jqYesanyInstall scripts
PythonYes3.11+CLI runtime (installable via uv)
uvRecommendedlatestPython + package manager in one binary
psqlOptionalanykindo db tunnels and prompts
Registry credsYesn/aregistry.kindo.ai username + password (provided by Kindo)

Step 1: Install Python 3.11+ (skip if you already have it)

Section titled “Step 1: Install Python 3.11+ (skip if you already have it)”

Check your current Python version:

Terminal window
python3 --version

If it reports 3.11 or higher, skip to Step 2. Otherwise install uv, which will also install Python 3.11 for you.

Terminal window
curl -LsSf https://astral.sh/uv/install.sh | sh
export PATH="$HOME/.local/bin:$PATH"
uv --version

Log in to the Kindo registry, then pull and unpack the artifact at your target release version:

Terminal window
helm registry login registry.kindo.ai --username <registry-username>
helm pull oci://registry.kindo.ai/kindo-cli/kindo-cli --version <release> --untar

Verify the wheel checksums before installing:

Terminal window
(cd kindo-cli && sha256sum -c SHA256SUMS)

For air-gapped installs, pull the .tgz on a connected machine and transfer it to the install host, or pull from a registry that mirrors registry.kindo.ai.

Recommended: via uv (isolates the CLI in its own environment):

Terminal window
uv tool install --force ./kindo-cli/kindo_cli-*.whl

Alternative: via pip (if you already have Python 3.11+):

Terminal window
python3.11 -m pip install ./kindo-cli/kindo_cli-*.whl

The glob matches the version in the wheel’s filename.

To upgrade, pull the target release and re-run the same install command.

Terminal window
kindo --version
kindo --help

If the help output prints, the CLI is installed and on your PATH. If kindo is not found, ensure $HOME/.local/bin is on PATH (uv installs CLIs there by default).

The config wizard produces the two files every subsequent command reads:

  • install-contract.yaml: what to deploy (version, base domain, registry credentials, superadmin email (operatorEmail), applications, peripheries, secrets-store backend, SMTP, optional integrations).
  • environment-bindings.yaml: where your infrastructure lives (PostgreSQL admin connection strings, an optional dedicated Hatchet instance, Redis, RabbitMQ, S3-compatible storage).

Run the wizard in the directory where you want the config files written:

Terminal window
kindo config init

Draft state is saved to .kindo-config-draft.json as you go, so you can quit with Ctrl-C and resume later.

  1. Deployment Basics: base domain, application version, storage class.

  2. Container Registry: registry.kindo.ai username and password (provided by Kindo).

  3. Operator: your email address. kindo install --apply creates this account as the install’s first superadmin and prints its key once.

  4. PostgreSQL: main admin connection string, auxiliary admin connection string (the same as main is fine for a single instance), and an optional Hatchet admin connection string. A dedicated instance for Hatchet is recommended; press Enter to keep Hatchet on the auxiliary instance. The optional connection is written as postgres.adminHatchet in environment-bindings.yaml. See PostgreSQL settings for Hatchet.

  5. Redis: connection string for the standalone Redis.

  6. RabbitMQ: connection string for RabbitMQ (AMQP or AMQPS).

  7. Object Storage: S3-compatible bucket name, region, optional static access/secret keys, optional custom endpoint for a store other than AWS S3. If that endpoint is only resolvable inside the cluster, also give a browser-reachable public endpoint for the same storage so download links work for end users.

  8. SMTP: an authenticated SMTP relay is required. Provide the host, user, password, and From address; port defaults to 587.

  9. Secrets Store: kubernetes (Secrets stored in the cluster, the default), azurekv (Azure Key Vault), vault (HashiCorp Vault), or aws (AWS Secrets Manager). External backends prompt only for that backend’s connection and auth fields. If you choose an external backend, complete External Secrets Store first.

  10. Integrations (optional): toggle MCP integrations here, or add them later with kindo integrations enable <name>.

If you quit partway through, re-running kindo config init detects the draft and offers to continue where you left off:

Found incomplete wizard draft (completed through section 4/10)
Continue where you left off? [Y/n]:

If the final install-contract.yaml and environment-bindings.yaml files already exist, the wizard offers to load them as defaults and edit in place.

On success the wizard prints:

✓ Configuration files created
install-contract.yaml
environment-bindings.yaml
Full example install-contract.yaml
apiVersion: kindo.ai/v1
metadata:
schemaVersion: '2'
baseDomain: kindo.example.com
appVersion: <release>
storageClass: <storage-class>
registry:
username: <registry-username>
password: <registry-password>
applications:
api: true
next: true
superadmin: true
litellm: true
credits: true
cerbos: true
prisma-migrations: true
hatchet: true
nango: true
task-worker-ts: true
mcp-unified: true
mcp-platform: true
sandbox: true
peripheries:
qdrant: true
presidio: true
speaches: true
otel-collector: true
operatorEmail: operator@example.com
secretsStore:
backend: kubernetes
smtp:
host: smtp.example.com
port: '587'
user: <smtp-user>
password: <smtp-password>
fromEmail: noreply@kindo.example.com
logLevel: info
ingress:
enabled: true
className: nginx
annotations:
nginx.ingress.kubernetes.io/force-ssl-redirect: 'true'
nginx.ingress.kubernetes.io/proxy-body-size: '0'
tls:
mode: cert-manager
clusterIssuer: letsencrypt-prod

The ingress: block is optional. With it omitted, route the hosts from kindo ingress manifest through your own controller. With enabled: true, the CLI renders one Ingress per host through className, applies annotations to every Ingress, and handles TLS per tls.mode: cert-manager, secret, acm (AWS Certificate Manager), or none. Hosts follow endpoints and baseDomain.

The example uses ingress-nginx with cert-manager. Each host gets cert-manager.io/cluster-issuer: letsencrypt-prod and a spec.tls entry with secret <release>-<instance>-tls (for example api-main-tls). For streaming chat responses on nginx, set the api release’s buffering and timeouts:

Terminal window
kindo config helm-override set api \
'ingress.annotations.nginx\.ingress\.kubernetes\.io/proxy-buffering=off' \
'ingress.annotations.nginx\.ingress\.kubernetes\.io/proxy-read-timeout=3600' \
'ingress.annotations.nginx\.ingress\.kubernetes\.io/proxy-send-timeout=3600'

For other ingress controllers, use the following examples:

AWS Load Balancer Controller with an ACM certificate
ingress:
enabled: true
className: alb
annotations:
alb.ingress.kubernetes.io/group.name: kindo-shared
alb.ingress.kubernetes.io/scheme: internet-facing
alb.ingress.kubernetes.io/target-type: ip
alb.ingress.kubernetes.io/listen-ports: '[{"HTTPS":443}]'
alb.ingress.kubernetes.io/ssl-redirect: '443'
tls:
mode: acm
acmCertificateArn: arn:aws:acm:us-west-2:123456789012:certificate/<certificate-id>

On alb, set load-balancer-level keys (scheme, certificate, listeners, security groups) only in the contract’s ingress.annotations; a per-release override of them is rejected at install and upgrade.

Kong Ingress Controller with a pre-provisioned wildcard certificate
ingress:
enabled: true
className: kong
annotations:
konghq.com/protocols: https
konghq.com/https-redirect-status-code: '301'
konghq.com/strip-path: 'false'
tls:
mode: secret
secretName: kindo-wildcard-tls

Create kindo-wildcard-tls in each publishing namespace. Each host gets a spec.tls entry referencing that Secret. For SSE on api, raise Kong’s route timeouts (milliseconds):

Terminal window
kindo config helm-override set api \
'ingress.annotations.konghq\.com/read-timeout=3600000' \
'ingress.annotations.konghq\.com/write-timeout=3600000'

mcp-platform (the agent tools adapter) is installed by default, and new installs turn on Platform tools and MCP account routing through the PLATFORM_MCP and MCP_ACCOUNT_ROUTING feature flags. MCP account routing lets users select more than one integration connection for a tool server. To opt out of the adapter, set applications.mcp-platform: false and turn off PLATFORM_MCP in Unleash at https://unleash.<domain>.

The superadmin dashboard is installed by default and served at superadmin.<domain>. Publish its DNS record with the others (DNS subdomains).

Full example environment-bindings.yaml
apiVersion: kindo.ai/v1
postgres:
admin:
connectionString: postgresql://<admin-user>:<admin-password>@postgres.example.com:5432/postgres?sslmode=require
adminAuxiliary:
connectionString: postgresql://<admin-user>:<admin-password>@postgres-auxiliary.example.com:5432/postgres?sslmode=require
adminHatchet:
connectionString: postgresql://<admin-user>:<admin-password>@postgres-hatchet.example.com:5432/postgres?sslmode=require
redis:
connectionString: redis://redis.example.com:6379
rabbitmq:
connectionString: amqps://<rabbitmq-user>:<rabbitmq-password>@rabbitmq.example.com
storage:
bucketName: kindo-uploads
region: <region>
accessKey: <storage-access-key>
secretKey: <storage-secret-key>
endpointUrl: https://storage.example.com
endpointUrlPublic: https://downloads.example.com

Check that your cluster can reach the dependencies listed in environment-bindings.yaml:

Terminal window
kindo config validate --preflight

kindo config validate checks the configuration schema. With --preflight, it also:

  1. Creates the kindo-system namespace if missing.

  2. Uploads your environment-bindings.yaml as a Kubernetes Secret (kindo-preflight-config).

  3. Creates the image-pull secret for registry.kindo.ai and performs a helm registry login.

  4. Deploys the preflight chart into kindo-system.

  5. Waits up to about five minutes for the kindo-preflight Job to finish and shows its logs.

The checks run inside your cluster, in this order:

  • PostgreSQL: main and auxiliary admin endpoints reachable, credentials valid.
  • Hatchet database: the Hatchet instance and its recommended settings; see PostgreSQL settings for Hatchet.
  • Redis: reachable on the provided connection string.
  • RabbitMQ: reachable (AMQP or AMQPS).
  • S3-compatible storage: bucket accessible with the provided credentials or workload identity. Warns when the storage endpoint resolves only inside the cluster and no public endpoint is set, because user download links would then be unreachable; see S3-compatible object storage.
  • SMTP: handshake and authentication.
  • DNS: baseDomain and its derived subdomains resolve.
  • Nodes: enough ready nodes.
  • StorageClass: the configured storageClass resolves in the cluster.
  • cert-manager: present when TLS is expected.
  • Ingress controller: at least one IngressClass is available.

--plan provides a read-only preview of the install:

Terminal window
kindo install --plan

It prints:

  • Install settings: domain, version, Postgres host (password masked), storage bucket.
  • Prerequisites: a green tick per required tool and a dot per optional tool.
  • Cluster reachability: confirms your kubeconfig can talk to the cluster.
  • Steps: the ordered list of install steps with their one-line descriptions.

The plan also flags a missing operatorEmail.

Once the plan looks right, run the install:

Terminal window
kindo install --apply

--apply stops before changing anything if operatorEmail is missing from install-contract.yaml.

The installer runs these steps in order:

#StepPurpose
1generate-secretsGenerate secrets and store them in the cluster.
2merge-bindingsMerge infrastructure bindings into the secrets config.
3setup-registryConfigure image pull secrets in all namespaces.
4external-secretsInstall the External Secrets Operator (operator, CRDs, webhook). The installer uses an existing ESO if it serves external-secrets.io/v1 (ESO 0.17 or later). An older ESO fails the step with upgrade guidance.
5db-bootstrapCreate databases and service users.
6peripheriesDeploy Unleash (+ Edge), Qdrant, Presidio, Speaches, Hatchet, the OTel Collector, ClickHouse and HyperDX. See Observability.
7hatchet-tokenStore the Hatchet API token in the secrets config.
8migrationsRun database schema migrations.
9applicationsDeploy the application services enabled in install-contract.yaml. The wizard enables api, next, superadmin (the superadmin dashboard), litellm, credits, cerbos, prisma-migrations, hatchet, nango, task-worker-ts, mcp-unified, mcp-platform, and sandbox.
10superadmin-bootstrapCreate the superadmin account for operatorEmail and print its key once. Re-runs print already bootstrapped and issue no key. The step fails if no superadmin exists afterwards.
11post-installNango bootstrap and feature flags.
12integrationsReconcile enabled integrations. Skips when none are enabled. A failure is recorded and reported, and the install finishes with a warning that integrations failed. Re-run with kindo install --step integrations.

During execution the CLI streams a live table of Helm release states (◐ pending, ✓ deployed, ✗ failed) plus the notable lines from scripts ([STEP], [INFO], [WARN]). Add --verbose to see the raw helmfile output.

After a failure (or an intentional Ctrl-C), resume with:

Terminal window
kindo install --resume

--resume stops before changing anything if operatorEmail is missing. It skips steps marked completed in kindo-install-state and picks up at the first unfinished step. Existing generated secrets are preserved.

If the contract has changed since the last run, the CLI warns you:

Warning: Config changed since last run. Verify changes are intentional.

The warning means you changed the config files between runs. Check that the change was intended. The install continues.

Re-run a single step by name:

Terminal window
kindo install --step applications
kindo install --step post-install
kindo install --step hatchet-token

--step runs the named step independently and updates its state in kindo-install-state on completion. Use it to retry a failed step.

Use --release <name> to narrow a group step to one release:

Terminal window
kindo install --step applications --release api

kindo install --step superadmin-bootstrap re-runs the bootstrap. If a superadmin already exists, it issues no key. It exits non-zero if no superadmin exists afterwards.

kindo install --step sync-flags adds feature flags new in the target version without changing existing ones.

Install state lives in the cluster. Anyone with cluster access and the CLI can observe or resume an install.

Terminal window
kindo status

Prints Version, Started, Contract hash, and a per-step table:

Kindo Install State
Version: 2026.09.0
Started: 2026-09-25T14:02:17
Contract hash: 7c0a3e...
Step Status Completed Error
generate-secrets ✓ completed 2026-09-25T14:02:35
merge-bindings ✓ completed 2026-09-25T14:02:41
setup-registry ✓ completed 2026-09-25T14:02:48
external-secrets ✓ completed 2026-09-25T14:04:12
db-bootstrap ✓ completed 2026-09-25T14:05:02
peripheries → running
hatchet-token ○ pending
migrations ○ pending
applications ○ pending
superadmin-bootstrap ○ pending
post-install ○ pending
integrations ○ pending

Below the step table, the Superadmin: line reports whether the install has a superadmin, for example ✓ bootstrapped. A live release table follows with each release’s chart version, image tags, and health. Use kindo status --no-live to skip the live checks. See Operate for interpreting status and diagnosing failures.

State is stored in the kindo-install-state ConfigMap in the kindo-system namespace:

Terminal window
kubectl -n kindo-system get cm kindo-install-state -o yaml

Keys:

  • meta.json: { version, contract_hash, started_at }
  • step.<name>: { status, started_at, completed_at, error } for each step, where status ∈ { pending, running, completed, failed, skipped }.

The ConfigMap is created on first run and labeled app.kubernetes.io/managed-by=kindo-cli. The CLI manages lifecycle; do not edit it by hand except in break-glass scenarios.

All cluster-resident secrets (generated passwords, API keys, encryption keys, merged infrastructure connection strings, Hatchet client token) live in the kindo-secrets-config Secret in kindo-system:

Terminal window
kubectl -n kindo-system get secret kindo-secrets-config -o jsonpath='{.data.secrets\.yaml}' | base64 -d

Restrict who can read this Secret.

To enable, disable, and deploy integrations after the install, see kindo integrations.