KBkilterKB
admindev

kilter deploy is a Vercel-style one-command deploy: build locally, push, and the platform takes it from there. You never need kubectl, helm, or flux on your machine.

Quick start

kilter login --server <url> --token <tok>     # once per machine
kilter deploy --project <org>/<app> \
  --registry registry.netshire.com/<scope>

When it completes, the app is live at <app>.<cluster-domain> and appears as a tile in the portal. kilter logout clears local credentials.

What happens under the hood

  1. Build local — the CLI builds the app image on your machine.
  2. Push — the image goes to the kilter-platform registry you named with --registry.
  3. POST to kilter-server — the CLI sends the deploy request; the server upserts a KilterApp custom resource in the target cluster.
  4. Operator renders — the in-cluster operator provisions credentials and renders the kilter-app Helm chart from the CR.
  5. Flux commit — the operator commits the rendered manifests to the cluster's flux repo; Flux reconciles them.
  6. Live — the cluster Gateway routes <app>.<cluster-domain> to your pods.

Environments and promotion

--env <name> targets a named deployment slot — prod (the default), dev, alpha, pr-247. Each env gets its own namespace, scoped database, and URL. Promotion is deploying the same project to the next slot up:

kilter deploy --env dev        # iterate here
kilter deploy --env prod       # promote: same command, protected env

For a brand-new env end-to-end (CI PR previews), --bootstrap provisions the namespace, scoped DB, and secrets, auto-plans and applies the initial migration, seeds, and blocks until pods are actually Ready — one command instead of a three-step dance. Default timeout is 15m (--timeout to raise it). Use plain kilter deploy for redeploys of an existing env.

Slow migrations can fail a bootstrap

The operator's readiness deadline (default 15m, KILTER_READINESS_TIMEOUT) starts when the env provisions and includes the migrate+seed window. If migrate + seed + startup can exceed 15m, raise both the operator timeout and the CLI --timeout, or the env may flip to Failed mid-bootstrap.

Migrations and seeds

FlagWhat it does
--migrationRun the schema migration as a Job before the new pod rolls
--seedAfter migration, apply each seed: entry from kilter.yaml in order, as Jobs, before the pod rolls

Combined order: image push → drift preflight (atlas only) → migration → seeds in declared order → app deploy. Any failed step halts the rest, and the previous pod keeps serving until the next attempt. kilter seed apply --dry-run previews each seed entry's plan without touching the cluster.

Drizzle / Prisma / SQL — migrations are committed files in the repo (drizzle/, prisma/migrations/, migrations/); --migration runs the tool's migrate deploy in the operator Job. Commit history is the review surface.

Atlas — declarative schema (schema/schema.sql) needs a reviewed plan before anything destructive:

kilter migrate plan --env prod                          # prints the SQL diff, saves a plan
kilter deploy --env prod --migration --plan-id plan-…   # apply it with the deploy

On protected envs (prod by default), plain kilter deploy runs the same plan check automatically: an empty diff proceeds silently; a non-empty diff refuses with the diff preview and the --plan-id next step (--skip-migration-check overrides). Ephemeral envs (dev, pr-*) skip the check.

Secrets: declarative, SOPS-encrypted

secrets: creates a Secret from a local KEY=VALUE file; appSecrets: consumes it into the primary app container via envFrom:

sopsRecipient: age1...                                 # cluster's PUBLIC age key — safe to commit
secrets:
  myapp-secrets: secrets/myapp.env                     # CREATE (git-ignore the file!)
appSecrets:
  - myapp-secrets                                      # CONSUME into the app
environments:
  pr-*:
    secrets:
      myapp-secrets: secrets/pr.env                    # per-env override

On deploy, the CLI SOPS-encrypts the file locally with the public recipient — plaintext never reaches kilter-server or git. Requires the sops CLI on PATH.

Secrets are pruned if a deploy can't read them

Every deploy re-ships the declared secrets. A deploy from an environment that can't read the source files (a CI runner missing the env files or the recipient) ships an empty set, and Flux's prune then deletes the live Secret — the app breaks on the next pod restart. Always deploy from somewhere that can resolve the secret files, or omit the secrets: declaration on deploys that shouldn't touch them.

Internal-only apps

Set public: false in kilter.yaml to skip the external route. The app gets only its in-cluster Service (http://<app>.<namespace>.svc:<port>), and its NetworkPolicy closes the port to other namespaces. Flip it back and the next deploy re-publishes; nothing else is torn down.

Process types

A background worker sharing the app's codebase and env is a process, not a separate component:

processes:
  worker: { command: [pnpm, worker:temporal] }

Processes reuse the app's built image and inherit its full env bag (database URL, secrets, service config) with no re-declaration. A process with port: 0 (the default) gets no Service or route.

Multi-user deploys

When several devs deploy to the same env, kilter serializes per (project, env): a second concurrent deploy blocks until the first finishes its cluster writes, and a migration attempted while one is in flight returns 409 Conflict — wait for it, don't retry. The apply Job re-owns schema objects after each migration so one dev's CREATE FUNCTION doesn't break the next dev's deploy. Not promised: latest-image-wins on the app step (coordinate via PR review).