Configure & Validate
After kindo install --apply finishes, take the deployment from running pods to users signing in: publish DNS, terminate TLS, sign in to the superadmin dashboard, create the first organization and its SSO, and run a smoke test.
1. Publish DNS records
Section titled “1. Publish DNS records”-
List the ingress endpoints your ingress controller has provisioned:
Terminal window kubectl get ingress -AEach ingress shows the external IP address or hostname of your ingress load balancer, usually the same one for every Kindo host. Example output:
NAMESPACE NAME CLASS HOSTS ADDRESSapi api-ingress nginx api.kindo.example.com 203.0.113.10next next-ingress nginx app.kindo.example.com 203.0.113.10superadmin superadmin-ingress nginx superadmin.kindo.example.com 203.0.113.10 -
Create DNS records for every hostname under your base domain. Either publish a wildcard or explicit records.
kindo ingress manifestlists every host.Wildcard (simplest):
*.kindo.example.com A 203.0.113.10If the load balancer has a hostname instead of an IP address, use a CNAME record pointing at it.
Explicit (when you need per-host firewall rules, specific target ports, or custom health checks):
Host Backend service Port Health check Access app.kindo.example.comnext80 /api/_healthAllowlisted (users) api.kindo.example.comapi80 /healthcheckAllowlisted (users + frontend); /webhookpublic unless a dedicated webhooks hostname is setintegrations-api.kindo.example.comnango80 /healthPublic (integration OAuth callbacks) integrations-connect.kindo.example.comnango-connect-ui3009 /Allowlisted unleash.kindo.example.comunleash4242 /healthAllowlisted unleash-edge.kindo.example.comunleash-edge3063 /internal-backstage/readyAllowlisted hatchet.kindo.example.com(/api/*)hatchet-api8080 /api/readyAllowlisted (task worker traffic) hatchet.kindo.example.com(/*)hatchet-frontend8080 /api/readyAllowlisted (admin UI) hyperdx.kindo.example.comhyperdx3000 /api/healthAllowlisted (your team) superadmin.kindo.example.com(/auth,/superadmin)api80 /healthcheckAllowlisted (your team) superadmin.kindo.example.com(/)superadmin80 /healthAllowlisted (your team) -
Wait for propagation, then confirm from outside the cluster:
Terminal window dig +short app.kindo.example.comdig +short api.kindo.example.com
Routing requirements that can’t be left at defaults
Section titled “Routing requirements that can’t be left at defaults”Configure these routes and settings on your ingress controller or WAF:
- Server-Sent Events on
/express/chat: the API streams chat responses as SSE. Your ingress must disable response buffering, allow read/send timeouts ≥ 300s, allow unlimited response body size, and preserve response content. In NGINX that’sproxy_buffering offplus generousproxy_read_timeout/proxy_send_timeout. In Traefik setrespondingTimeouts; in Envoy setstream_idle_timeout. Default buffering and 60s timeouts truncate chat responses. - Request header size (controller-wide, all hosts): auth tokens and session cookies can make Kindo’s request headers larger than some ingress controllers’ default header buffer. If clients get intermittent 400 Bad Request or 431 Request Header Fields Too Large, often only for some users or after login, raise the controller’s header buffer. Kindo’s backend accepts up to 16 KB of request headers, so the ingress must let a single header of that size through. See Header buffer sizes.
- Hatchet path-based routing on
hatchet.<domain>: route/api/*tohatchet-apiwith higher priority than/*, which routes to the admin UI (hatchet-frontend). - AWS Load Balancer Controller with a non-VPC CNI: see Provider notes.
- Superadmin dashboard routing on
superadmin.<domain>: route/authand/superadmintoapiahead of/tosuperadmin. Match/authand/superadminas path segments. With an AWS ALB IngressGroup, give theapirules the lowergroup.order.
Hatchet client identity and proxy trust
Section titled “Hatchet client identity and proxy trust”Hatchet defaults to SERVER_CLIENT_IP_MODE=legacy to preserve existing client
rate-limit buckets. Legacy mode trusts forwarded headers and remains vulnerable
to header spoofing. Enable and verify an appropriate client-IP mode to protect
client identity.
Before configuring this policy, confirm that your installed Kindo release notes explicitly include the Hatchet client-IP policy. Earlier releases ignore these settings. Do not infer availability from the release line alone. Configure the following values only after reviewing the rendered ingress/controller route:
| Setting | Value |
|---|---|
SERVER_CLIENT_IP_MODE | trusted-proxy for a verified proxy route, or direct when clients connect directly to Hatchet. |
SERVER_TRUSTED_PROXY_CIDRS | In trusted-proxy mode, a comma-separated list of the verified immediate and intermediate proxy CIDRs. Leave empty in direct mode. |
SERVER_CLIENT_IP_HEADER | In trusted-proxy mode, select X-Forwarded-For, X-Real-IP or CF-Connecting-IP to match the verified proxy behavior. Leave empty in direct mode. |
Verify the connection from the proxy to Hatchet. For X-Real-IP or
CF-Connecting-IP, the proxy must overwrite client-supplied values. For
X-Forwarded-For, it must append the actual client hop; Hatchet walks the chain
from the socket peer and stops at the first untrusted hop. Do not trust a broad
private network merely because the ingress runs there. direct mode ignores
forwarded headers and can combine clients into one bucket when used behind a
proxy.
For the current CLI’s config.yaml format, set each value separately. Replace
<verified-proxy-cidrs> with the reviewed comma-separated CIDRs; do not copy a
broad private-network range as a default:
kindo config set overrides.serviceEnv.hatchet.SERVER_CLIENT_IP_MODE=trusted-proxykindo config set 'overrides.serviceEnv.hatchet.SERVER_TRUSTED_PROXY_CIDRS=<verified-proxy-cidrs>'kindo config set overrides.serviceEnv.hatchet.SERVER_CLIENT_IP_HEADER=X-Forwarded-Forkindo config apply --plan --only hatchetkindo config apply --only hatchetFor a direct connection, set SERVER_CLIENT_IP_MODE=direct and leave the CIDR
and header values empty instead. These commands apply to the current CLI; use
the instructions shipped with an older release’s CLI rather than mixing config
formats during an upgrade.
After enabling the mode, use two test clients through the configured route to confirm they retain distinct client identities and rate-limit buckets. Repeat with forged forwarding headers and confirm the identity does not change. If a check fails, correct the proxy/header configuration before relying on the policy. This opt-in configuration does not require a database migration.
Unleash private integration destinations
Section titled “Unleash private integration destinations”Before upgrading an installation with private-network Unleash webhook or addon
URLs, confirm whether the target release notes require an integration allowlist.
Inventory the exact destination hostnames before rollout. The current CLI stores
chart overrides in config.yaml; older CLI releases use a different format.
Unleash uses an env list rather than an envData map. Run kindo config edit
and merge flat indexed entries under overrides.helmValues.unleash. Helm replaces
lists: preserve the complete existing env list, including
UNLEASH_DEFAULT_ADMIN_USERNAME and any IMPORT_FILE, IMPORT_PROJECT or
IMPORT_ENVIRONMENT entries used by your installation. Preserve all unrelated
configuration and secret references. If the list contains only the default admin
username, the resulting entries are:
overrides: helmValues: unleash: env[0].name: 'UNLEASH_DEFAULT_ADMIN_USERNAME' env[0].value: 'admin' env[1].name: 'UNLEASH_ALLOW_LIST_INTEGRATION' env[1].value: 'webhook.internal.example,alerts.internal.example'For a longer list, retain its entries at contiguous indexes and append the
allowlist at the next index; update an existing allowlist entry instead of
adding a duplicate. Replace any whole-value env override with the complete
indexed representation. Values are quoted strings, not a serialized YAML list.
Use only required hostnames, without a scheme, port, path or wildcard. Entries
match exact hosts, not subdomains or similarly named hosts.
On an already-upgraded installation, preview and apply the change:
kindo config get overrides.helmValues.unleashkindo config apply --plan --only unleashkindo config apply --only unleashWhen preparing an upgrade, save the configuration and follow the target
release’s upgrade procedure instead of applying a newer CLI’s configuration to
older running services. Check the rendered env list preserves existing entries
and test each configured integration after rollout. Do not enable
UNLEASH_ALLOW_PRIVATE_URL_IN_INTEGRATION; it permits arbitrary private
destinations and bypasses the exact-host restriction. Public HTTP/HTTPS
destinations do not need private-host entries.
Unleash custom webhook templates
Section titled “Unleash custom webhook templates”Before upgrading an installation with custom webhook body templates, send a test
event to each receiver and check the rendered payload. Double-brace substitutions
such as {{eventMarkdown}} and {{event.data.name}} HTML-escape their values:
slashes become /, and quotes, angle brackets and ampersands become entities.
Templates that relied on unescaped values can therefore change what their
receiver displays or parses. The default webhook body uses JSON serialization.
Keep double braces when inserting a value into HTML. Use a raw substitution such
as {{{eventMarkdown}}} only when the receiver intentionally accepts that raw
text and handles its content safely. Triple braces disable HTML escaping; they
do not JSON-escape quotes or backslashes. Do not substitute raw user-controlled
fields into a JSON or HTML body. For templates using the existing JSON-encoded
event string, retain {{{eventJson}}} and verify the receiver’s JSON decoding.
2. Terminate TLS at the ingress
Section titled “2. Terminate TLS at the ingress”If the contract’s ingress: block is enabled, the CLI renders every Ingress object and TLS follows ingress.tls.mode: acm, cert-manager, secret, or none. Follow this section when you route the hosts yourself (kindo ingress manifest lists them).
Choose one of the following options.
Option A: cert-manager (recommended)
Section titled “Option A: cert-manager (recommended)”-
Request certificates. With cert-manager and a
ClusterIssuer(Let’s Encrypt, private CA, Vault, etc.) installed, annotate each Ingress you created:Terminal window kubectl annotate ingress <name> -n <namespace> cert-manager.io/cluster-issuer=letsencrypt-prod --overwriteOr create a single
Certificateresource that covers every Kindo host and reference its secret from each ingress’stls:block. -
Verify certificate issuance:
Terminal window kubectl get certificate -Akubectl describe certificate <name> -n <namespace>
Option B: manual certs
Section titled “Option B: manual certs”-
Create Kubernetes TLS secrets from certs issued by your private CA or certificate vendor:
Terminal window kubectl create secret tls kindo-tls \-n <namespace> \--cert=tls.crt \--key=tls.key -
Reference the secret from each ingress. The secret name must match what the ingress
tls.secretNamereferences. If you use one wildcard cert for all hosts, create the secret in each ingress’s namespace (or use a tool like reflector to replicate it).
3. Sign in and set up the first organization
Section titled “3. Sign in and set up the first organization”-
Store the superadmin key. The
superadmin-bootstrapstep prints your key once. Store it in your secret manager immediately. Keys expire after 90 days by default. Replace a lost or expiring key from the superadmin dashboard under Keys > New key. -
Sign in to the superadmin dashboard. Open
https://superadmin.<domain>, enter the email you set asoperatorEmail, select Continue, then select Email me a code. Enter the 6-digit code from the email. -
Create the organization. Open Organizations > Create organization and fill in Organization name, First administrator email, and Domains. The first administrator email becomes the organization’s Admin.
-
Verify its domain. Verify ownership of the domain yourself, then open the organization’s Domains tab > Edit domains, turn on Verified for the domain, and select Save domains. The dashboard records your assertion. SSO requires at least one verified domain.
-
Impersonate an Admin. In the organization’s Users tab, open an active Admin user’s actions menu and select Impersonate… > Prepare session > Open customer app. The session grants full Admin access for one hour. Actions are audited as that user and attributed to you. Use a separate browser profile to keep an existing app sign-in; opening the customer app replaces the app sign-in in that browser profile.
-
Set up SSO in the app. Open Settings > SSO and select Create SAML connection. Follow SSO Setup for the connection and IdP steps. For an install upgraded from 2026.07, see Restore SSO for each organization.
-
Run the smoke test. Complete the smoke test to confirm users can sign in and chat.
4. Limit the sign-in methods shown
Section titled “4. Limit the sign-in methods shown”The app’s /login offers SAML SSO, an emailed one-time code, and, once configured, Google. To offer SAML only, or SAML plus one of the others, hide the other methods. The toggles control button visibility; the API routes remain available.
The toggles are env vars on the next release. A method is hidden only when the value is false (case-insensitive, surrounding whitespace ignored); unset means shown. Google is set to false by default. To offer it, set GOOGLE_OAUTH_CLIENT_ID and GOOGLE_OAUTH_CLIENT_SECRET on the api release, then set NEXT_PUBLIC_ENABLE_GOOGLE_LOGIN=true.
kindo config override set next NEXT_PUBLIC_ENABLE_GOOGLE_LOGIN=false NEXT_PUBLIC_ENABLE_EMAIL_OTP_LOGIN=falsekindo config override apply nextOn an install that delivers secrets from an external store, kindo config override refuses to run. Set the same keys as chart values:
kindo config helm-override set next envData.NEXT_PUBLIC_ENABLE_GOOGLE_LOGIN=false envData.NEXT_PUBLIC_ENABLE_EMAIL_OTP_LOGIN=falsekindo config helm-override apply nextTo bring a method back, remove the override with kindo config override unset next <KEY> or kindo config helm-override unset next <path> and re-run the matching apply. For Google, set the key to true instead.
The superadmin dashboard offers SSO to superadmins whose organization has an SSO connection. Google is hidden there by default too; set config.enableGoogleLogin=true to offer it. To hide Google and emailed-code sign-in on the dashboard, set both toggles to false:
kindo config helm-override set superadmin config.enableGoogleLogin=false config.enableEmailOtpLogin=falsekindo config helm-override apply superadminWith both toggles off, dashboard sign-in requires an SSO connection for the superadmin’s organization. A superadmin whose organization lacks a connection sees an error.
To bring emailed-code sign-in back, unset its key and re-run the apply (for Google, set its key to true instead):
kindo config helm-override unset superadmin config.enableEmailOtpLoginkindo config helm-override apply superadmin5. Smoke test
Section titled “5. Smoke test”5.1 Install state from the CLI
Section titled “5.1 Install state from the CLI”kindo statusEvery step should show completed, and the Superadmin: line should show ✓ bootstrapped. The integrations step may show failed; resolve its error and re-run it:
kindo install --step integrationsIf another step failed, resolve its error and resume:
kindo install --resume5.2 Pod health
Section titled “5.2 Pod health”kubectl get pods -A --field-selector=status.phase!=Running,status.phase!=SucceededExpect an empty list, or only pods still starting. Check each release’s health in the live release table from kindo status.
5.3 API health check
Section titled “5.3 API health check”curl -fsS https://api.kindo.example.com/healthcheckExpected: HTTP 200. If TLS fails, rerun the cert-manager verification from step 2. If you get a 502 / 504, check the API pod logs (kubectl logs -n api -l app.kubernetes.io/name=api).
5.4 Manual chat test
Section titled “5.4 Manual chat test”- Open
https://app.kindo.example.comin a private window. - Sign in as a user of the organization, using SSO once it is live, or an emailed code at
https://app.kindo.example.com/login. - Send a short message to the default model.
- Confirm the response streams back. If it arrives as a single chunk, your ingress is buffering. Revisit the routing requirements.
