Skip to content

Configure & Validate

By the time you arrive here, kindo install --apply has finished and every pod in the stack should be Running. This page covers the final mile: publishing DNS, terminating TLS at the ingress, wiring up SSO, seeding the first org and admin user, and running a smoke test that proves the install is truly usable.

If the CLI’s post-install step (the final step of kindo install --apply) completed cleanly, most of the SSO and admin-user work below is already done — this page shows you how to verify it and tells you what to do when you need to adjust.

What ships out of kindo install

The post-install step performs these bootstrap actions automatically:

  • Runs SSOReady database migrations.
  • Creates the first Enterprise org and admin user in the Kindo database from adminUser.email and adminUser.name in install-contract.yaml.
  • Runs the SSO bootstrap using adminUser.ssoDomain — seeds SSOReady infrastructure rows, creates or updates the SSOReady organization, links it to the Kindo org, and prints a one-time self-serve setup URL.
  • Registers any models defined in models[] and imports feature flags to Unleash.

What’s still left to do:

  1. Publishing DNS
  2. Terminating TLS
  3. Running the SAML connection through the setup URL
  4. Smoke-testing the result

1. Publish DNS records

  1. List the ingress endpoints your ingress controller has provisioned:

    Terminal window
    kubectl get ingress -A

    On AWS with the ALB controller, expect one ALB hostname shared across all Kindo services. On NGINX / Traefik / cloud LB controllers, you’ll see a single external IP or hostname attached to each ingress. Example output:

    NAMESPACE NAME CLASS HOSTS ADDRESS
    api api-ingress alb api.kindo.example.com k8s-kindoshared-abc.us-west-2.elb.amazonaws.com
    next next-ingress alb app.kindo.example.com k8s-kindoshared-abc.us-west-2.elb.amazonaws.com
    ssoready ssoready-ingress alb sso.kindo.example.com ... k8s-kindoshared-abc.us-west-2.elb.amazonaws.com
  2. Create DNS records for every hostname under your base domain. Either publish a wildcard or explicit records — both work.

    Wildcard (simplest):

    *.kindo.example.com CNAME k8s-kindoshared-abc.us-west-2.elb.amazonaws.com

    Explicit (when you need per-host firewall rules, specific target ports, or custom health checks):

    HostBackend servicePortHealth checkAccess
    app.kindo.example.comnext80/api/_healthWhitelisted (users)
    api.kindo.example.comapi80/healthcheckWhitelisted (users + frontend)
    integrations-api.kindo.example.comnango80/healthPublic — integration OAuth callbacks
    integrations-connect.kindo.example.comnango-connect-ui3009/Whitelisted
    sso.kindo.example.comssoready-admin80/healthWhitelisted
    sso-auth.kindo.example.comssoready-auth80/healthWhitelisted (SAML auth flow)
    sso-api.kindo.example.comssoready-api80/healthWhitelisted
    sso-app.kindo.example.comssoready-app80/healthWhitelisted
    hatchet.kindo.example.com (/api/*)hatchet-api8080/api/readyWhitelisted (task worker traffic)
    hatchet.kindo.example.com (/*)hatchet-frontend8080/api/readyWhitelisted (admin UI)
    litellm.kindo.example.comlitellm4000/health/livelinessWhitelisted
    unleash.kindo.example.comunleash4242/healthWhitelisted
    unleash-edge.kindo.example.comunleash-edge3063/healthWhitelisted
  3. Wait for propagation, then confirm from outside the cluster:

    Terminal window
    dig +short app.kindo.example.com
    dig +short api.kindo.example.com

Routing requirements that can’t be left at defaults

A few ingress rules ship with non-obvious constraints. If your ingress controller or WAF is applying default behavior, Kindo features will break in ways that are hard to diagnose later:

  • Server-Sent Events on /express/chat — the API streams chat responses as SSE. Your ingress must disable response buffering, allow read/send timeouts ≥ 300s, impose no response body size limit, and apply no content transformation on responses. In NGINX that’s proxy_buffering off plus generous proxy_read_timeout / proxy_send_timeout. In Traefik set respondingTimeouts; in Envoy set stream_idle_timeout. Default buffering + 60s timeouts will silently truncate every chat response.
  • Hatchet path-based routing on hatchet.<domain> — the host fronts two backends. /api/* must route to hatchet-api with higher priority than /* which routes to the admin UI (hatchet-frontend). Wrong priority sends task-worker traffic to the UI and nothing works.
  • Non-VPC CNIs (Calico, Cilium) — the AWS ALB Controller’s default target-type=ip cannot reach pod IPs on non-VPC CNIs. Either switch affected services to NodePort and use target-type=instance, or run an in-cluster ingress (NGINX, Traefik, Istio) instead of the ALB controller.

2. Terminate TLS at the ingress

You have two paths, pick one.

If your cluster has cert-manager installed and a ClusterIssuer (Let’s Encrypt, private CA, Vault, etc.), annotate the Kindo ingresses to request certificates automatically:

Terminal window
kubectl annotate ingress -A \
-l app.kubernetes.io/part-of=kindo \
cert-manager.io/cluster-issuer=letsencrypt-prod --overwrite

Or create a single Certificate resource that covers every Kindo host and reference its secret from each ingress’s tls: block.

Verify cert issuance:

Terminal window
kubectl get certificate -A
kubectl describe certificate <name> -n <namespace>

Option B — manual certs

Create Kubernetes TLS secrets from certs issued by your private CA or certificate vendor, then reference them from each ingress:

Terminal window
kubectl create secret tls kindo-tls \
-n <namespace> \
--cert=tls.crt \
--key=tls.key

The secret name must match what the ingress tls.secretName references. 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. Verify the first org and admin user

The post-install step reads these fields from install-contract.yaml:

install-contract.yaml
baseDomain: kindo.example.com
adminUser:
email: [email protected] # creates the first Enterprise org + Admin user
name: Admin User # display name (default: "Admin User")
ssoDomain: example.com # if set, also triggers SSO bootstrap for this domain
bootstrapAdmin:
enabled: true # enables password-based break-glass admin login
password: <strong-password> # required when enabled=true

Both adminUser and bootstrapAdmin are defined in tools/kindo-cli/src/kindo_cli/config/contract.py. adminUser drives the SSO-backed Kindo admin; bootstrapAdmin is a password-backed break-glass account for first login and recovery — keep its password in your secret vault.

Verify the admin user exists by querying the main database. The simplest route is via the CLI’s DB tunnel:

Terminal window
kindo db tunnel main --print
# copy the DATABASE_URL it prints, then:
psql "$DATABASE_URL" -c 'SELECT id, email, "orgRole" FROM "User";'

You should see one row with the email you set in adminUser.email and orgRole = 'Admin'.

4. Finish SSO via SSOReady

The post-install step has already:

  • Seeded SSOReady’s app_organizations, environments, api_keys, and saml_oauth_clients tables.
  • Created an SSOReady organization keyed on adminUser.ssoDomain (external ID + domain).
  • Linked it to the Kindo org by writing ssoReadyOrgId onto the Org row.
  • Generated a one-time self-serve setup URL and printed it to stdout.

The SSOReady “Admin” role: SSOReady ships four services — ssoready-auth, ssoready-api, ssoready-app, and ssoready-admin. The ssoready-admin UI (https://sso.<baseDomain>) is for SSOReady administrators — people who manage SAML connections and organizations inside SSOReady itself. It is distinct from the Kindo admin role. post-install provisions an admin session token for this UI and prints a console-snippet to sign in; keep that snippet with your break-glass credentials.

  1. Recover the setup URL if you lost the original output. Re-run the post-install step — it is idempotent and will reprint the self-serve setup URL:

    Terminal window
    kindo install --step post-install
  2. Hand the setup URL to your IdP admin. They complete the SAML connection in Okta / Entra / Google / JumpCloud / ADFS / Ping. The canonical attribute mapping is email, firstName, lastName; see SSO Setup for provider-specific guides.

  3. Verify the connection back in the SSOReady admin UI (https://sso.<baseDomain>). The SAML connection should appear and JIT provisioning is enabled automatically.

  4. Adjust adminUser / bootstrapAdmin later by editing the contract with kindo config edit and re-running the post-install step:

    Terminal window
    kindo config edit
    kindo install --step post-install

    The post-install step is idempotent — it skips existing users and orgs, and the SSO bootstrap within it patches the SSOReady organization if the display name or domain changed.

5. Smoke test

Run every one of these. The stack is only “configured” once they all pass.

5.1 Install state from the CLI

Terminal window
kindo status

Every step from generate-secrets through post-install should show completed. If anything is failed, resolve it before continuing with the rest of this page — kindo install --resume picks up from the failed step.

5.2 Pod health rollup

Terminal window
for ns in api next litellm llama-indexer ssoready cerbos task-worker-ts \
external-poller external-sync credits audit-log-exporter \
unleash unleash-edge qdrant presidio speaches hatchet nango; do
echo "=== $ns ==="
kubectl get pods -n "$ns" 2>/dev/null
done

Every pod should be Running with the expected READY count (e.g. 1/1, 2/2). Jobs (migrations, bootstrap) are expected to be in Completed.

5.3 API health check

Terminal window
curl -fsS https://api.kindo.example.com/healthcheck

Expected: 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 via the UI

  1. Open https://app.kindo.example.com in a private window.
  2. Click Sign in with SSO, enter the SSO domain you configured, and complete the IdP login.
  3. Send a short message to the default model.
  4. Confirm the response streams back. If it doesn’t stream at all (response arrives as a single chunk), your ingress is buffering — revisit the streaming requirements in AWS Applications — Webhooks and Streaming.

If SSO is not yet live, you can log in with the bootstrapAdmin password instead by pointing the browser at https://app.kindo.example.com/login and using the email from adminUser.email plus the bootstrapAdmin.password value.

Where to next