kilter.yaml sits at your project root and configures both the dev environment and production deploys. It is deliberately minimal: every field is optional, and auto-detection fills in sensible defaults. Most projects start with an empty (or absent) file and only add what they need.
Example
# All fields are optional. Auto-detection fills in defaults.
name: my-app # Project name (default: directory name)
port: 3000 # App port (default: parsed from dev command, e.g. vite --port 3001)
health: /api/health # Health check path
image: node:22-alpine # Base Docker image
dev: npm run dev # Dev command
skills: true # Generate agent skills (default: true, set false to disable)
# Services — simple list or inline overrides
services:
- postgres # Catalog reference
- redis
- name: imgproxy # Inline custom service
image: darthsim/imgproxy:latest
port: 8080
provides:
IMGPROXY_URL: "http://imgproxy.{{ .Namespace }}.svc:8080"
# Environment variables injected into the app container
env:
CUSTOM_VAR: "value"Top-level fields
| Field | Default | Purpose |
|---|---|---|
name | directory name | Project name — also names the cluster, namespace, and cache dir |
port | parsed from the dev command | Port the app listens on |
health | — | Health check path for readiness probes |
image | detected from the framework | Base Docker image for the dev container |
dev | detected from package.json etc. | Command that starts the app in dev |
skills | true | Generate agent skills into the project; set false to disable |
Services
The services: list declares backing services. Two shapes work:
Catalog reference — a bare string names a service from kilter catalog:
services:
- postgres
- redisInline override — an object defines or customizes a service in place:
services:
- name: imgproxy
image: darthsim/imgproxy:latest
port: 8080
provides:
IMGPROXY_URL: "http://imgproxy.{{ .Namespace }}.svc:8080"provides: declares env vars the service injects into the app container; {{ .Namespace }} is templated to the project namespace at render time.
Older configs override services with a top-level block like postgres: { database: my_db }. That syntax still works but is deprecated — use the inline object form inside services: instead.
Add services with kilter add <service> rather than editing by hand — it updates the file and re-renders artifacts in one step.
Env block
env: injects plain environment variables into the app container:
env:
CUSTOM_VAR: "value"Use this for non-secret configuration. Service connection strings come from each service's provides: — you don't declare DATABASE_URL here.
Manifest block
The optional manifest: block feeds the kilter portal's discovery registry (ADR-0016). It changes nothing about how the app runs — it describes the app to the platform.
manifest:
description: "Customer-facing billing dashboard"
owner: platform-team
tags: [billing, internal]
links:
docs: https://kb.example.com/billing
runbook: https://kb.example.com/billing/runbook| Field | Purpose |
|---|---|
description | One-line summary shown on the app's portal tile |
owner | Team or person responsible |
tags | Free-form labels for filtering in the portal |
links | Map of named URLs; docs and runbook are the conventional keys. A repo link is auto-filled from the git remote at deploy time |
agent | Declares a2a/mcp paths; apps with this set are classified as Agents in the portal |
Key concepts
Isolated kubeconfig
Kilter never touches ~/.kube/config. Each project's kubeconfig lives at:
~/.cache/kilter/<name>/kubeconfig
Access the cluster directly with KUBECONFIG=~/.cache/kilter/<name>/kubeconfig kubectl ....
Port isolation
Each project gets deterministic, non-conflicting port allocations, so multiple kilter projects run side by side without collisions. kilter env prints your project's ports and connection strings.
Eject pattern
Generated artifacts (Tiltfile, Dockerfiles, chart values) live in ~/.cache/kilter/<name>/. Run kilter eject to copy them into your project for customization — once ejected, kilter won't overwrite them. kilter eject --list shows what's available.
Auto-detection
Kilter scans package.json, go.mod, Cargo.toml, and pyproject.toml to detect:
- Framework (Next.js, Go, Rust, Python)
- Services (
postgresfrompg/drizzle-orm,redisfromioredis, and so on) - Port, dev command, and base image
Anything you set explicitly in kilter.yaml wins over detection.
Production-only fields
Fields like public: false, secrets:, appSecrets:, processes:, and environments: only apply to kilter deploy. They're covered in Deploying to Production.