KBkilterKB
dev

Wiring Catalog Services

Redis, NATS, ClickHouse, Meilisearch, Qdrant — most backing services wire the same way. This is the generic loop; auth, storage, workflows, and observability have dedicated runbooks that override it (Auth, Storage, Temporal, SigNoz).

1. Discover — don't guess

kilter catalog                  # list available services
kilter catalog show <service>   # category, provides (env vars), detect (client lib)
kilter add <service>            # add to kilter.yaml and re-render
kilter env                      # resolved connection strings + ports

provides tells you which env vars the service publishes into your app container; detect names the client library to install. Ports are deterministic per project but different across projects — always read them from kilter env, never hardcode.

2. Connect by category

CategoryEnvPatternClient lib
Database (postgres)DATABASE_URLconnection string → ORM initdrizzle / prisma / pg
Cache (redis)REDIS_URLconnection string → client initioredis / bullmq
Analytics DB (clickhouse)CLICKHOUSE_URL + admin Secretresolve password → client; batch inserts@clickhouse/client
Messaging (nats, kafka, rabbitmq)NATS_URL / KAFKA_BROKERSbroker URL → producer/consumernats / kafkajs / amqplib
Search (qdrant, meilisearch, typesense)<SVC>_URL (+ key Secret for some)API URL → client; create index in app codeper service
Email (mailpit)SMTP_HOST, SMTP_PORTSMTP → mail clientnodemailer
Don't assume no-auth

Most services need no credentials in dev, but some (clickhouse, meilisearch) keep an admin password or master key in a Kubernetes Secret rather than in provides. If kilter env doesn't surface a credential the client needs, read the service's Secret with kubectl get secret ... -o jsonpath=... (using the project kubeconfig at ~/.cache/kilter/<name>/kubeconfig) and put it in .env.local — never commit it.

3. Validate

Wiring is not done until a test proves connectivity.

kilter doctor                    # kilter.yaml ↔ code dependency alignment
kilter exec -- <health check>    # service reachable from inside the app container

Then write a minimal connectivity test under tests/wiring/<service>.test.ts:

// redis example — the shape varies by category
const redis = new Redis(process.env.REDIS_URL!);
expect(await redis.ping()).toBe('PONG');
await redis.quit();

Finish by confirming existing pages still load.

4. Document

Add the new env vars to .env.local.example (no secrets), and a line to the project docs if the service changes the dev workflow.

Inline custom services

Anything not in the catalog can be declared directly in kilter.yaml — an image, a port, and the env vars it should publish:

services:
  - postgres                # catalog reference
  - name: imgproxy          # inline custom service
    image: darthsim/imgproxy:latest
    port: 8080
    provides:
      IMGPROXY_URL: "http://imgproxy.{{ .Namespace }}.svc:8080"

The provides template renders with the project namespace and injects into the app container like any catalog service. Then kilter up picks it up.

Anti-patterns

  • Hardcoded URLs or ports — they're project-specific; read from kilter env.
  • Direct browser calls to cookie-based services — auth needs the same-origin proxy; see Auth: Wiring Ory.
  • Skipping validation — a service that starts is not a service that's wired.