TDK CLI: How a Simple service.json Becomes a Full Local Stack
Most multi-service setups still force you to maintain Docker Compose files, Tiltfiles, nginx configs, port maps, and health checks by hand. AI coding agents make the application code easy, but they are still bad at keeping the local infrastructure consistent.
TDK CLI solves this by treating a tiny declarative file as the source of truth and generating everything else.
The Contract
This is essentially all you write for a service:
{ "$schema": "https://tdk-landscape.github.io/schema.service.json", "schemaVersion": 1, "appName": "orders-api", "appType": "backend", "stack": "shop", "port": 4000, "healthCheckPath": "/health", "dependsOn": [] } From this single file, TDK generates Dockerfiles, live-reload rules, Traefik routes, shared Postgres wiring, health probes, and the Tilt configuration that actually runs it.
The project is organized as Project → Stack → Resource.
flowchart LR P[Project<br/>.tdk/project.json] --> S1[Stack: shop] P --> S2[Stack: billing] S1 --> R1[orders-api<br/>backend] S1 --> R2[orders-app<br/>frontend] S2 --> R3[checkout-api<br/>backend] Three Subsystems
The codebase is deliberately split so each part has a clear job:
flowchart TB subgraph CLI["cli/ (TypeScript)"] Commands[Commands & validation] Templates[Handlebars templates] Ports[Port allocation] end subgraph Discovery["discovery/ (Starlark)"] Scan[Scan service.json] DAG[Build dependency DAG] end subgraph Engine["engine/ (Starlark)"] LiveUpdate[live_update rules] Traefik[Traefik routing] Infra[Postgres + platform] end CLI -->|generates Tiltfile + configs| Discovery Discovery -->|registry + DAG| Engine Engine -->|docker_build + containers| Docker[(Docker)] | Subsystem | Language | Responsibility |
|---|---|---|
cli/ | TypeScript | Commands, validation, port allocation, template generation, launching Tilt |
discovery/ | Starlark | Scan for service.json files, normalize them, build the dependency DAG |
engine/ | Starlark | Runs inside Tilt — turns the DAG into real docker_build, live_update, Traefik, Postgres, etc. |
The CLI never starts containers itself. The engine never parses CLI flags. Discovery never builds images.
The Generation Step
The real glue is the template engine (cli/src/generator/template-engine.ts). It reads .tdk/project.json and emits files into the gitignored .tdk/.tdk-out/ directory:
Tiltfile-
TILT_TECH_STACK.starandTILT_RESOURCE_DEFAULTS.star spec.master.tiltignore
These come from Handlebars templates. The CLI also vendors a copy of the engine and discovery code so tdk up works offline.
What Happens When You Run tdk up
sequenceDiagram actor Dev participant CLI as TDK CLI participant Gen as Template Engine participant Tilt participant Engine as Starlark Engine participant Docker Dev->>CLI: tdk up shop CLI->>CLI: Preflight (Docker, schema, drift, ports) CLI->>Gen: Ensure master configs Gen-->>CLI: .tdk/.tdk-out/Tiltfile CLI->>Tilt: tilt up -f Tiltfile -- --focus=shop Tilt->>Engine: Load discovery + engine Engine->>Engine: Scan service.json → DAG Engine->>Docker: docker_build + live_update Engine->>Docker: Start Traefik + Postgres Docker-->>Tilt: Containers healthy Tilt-->>CLI: Ready CLI-->>Dev: Print URLs + Tilt UI High-level steps:
- Find the project root and run preflight checks.
- Plan host ports (prefer 80/443/5432, fall back if busy).
- Ensure master configs exist (regenerate if needed).
- Auto-enable newly discovered stacks into the
pre_alphaphase. - Spawn
tilt upagainst the generated Tiltfile. - Inside Tilt the engine generates per-service
.autogenerated/files, registers builds with live-update, and starts shared infrastructure. - The CLI waits for readiness and prints the real URLs.
Committed vs Generated
TDK is strict about this boundary so AI agents (and humans) cannot silently break the infrastructure:
flowchart LR subgraph Committed["Committed (source of truth)"] PJ[.tdk/project.json] SJ[services/*/*/service.json] Code[Your app source] end subgraph Generated["Generated (gitignored)"] Out[.tdk/.tdk-out/<br/>Tiltfile, specs] Auto[services/*/*/.autogenerated/] Env[.env] end Committed -->|tdk project / tdk resource / tdk up| Generated Generated -->|tdk config regenerate| Committed | Path | Status | Why |
|---|---|---|
.tdk/project.json | Committed | Project identity and phases |
services/*/*/service.json | Committed | Service contract |
| Application source | Committed | Your code |
.tdk/.tdk-out/ | Generated | Derived from templates |
services/*/*/.autogenerated/ | Generated | Engine output during Tilt |
.env | Generated | Local secrets |
What It Is Not
TDK is a local development runtime. It does not deploy to staging or production. The project explicitly leaves production to Helm, Argo CD, or whatever you already use. The local environment is optimized for speed and developer experience, not for being a perfect replica of production.
Getting Started
npm install -g @tdk-landscape/tdk-cli-core tdk doctor tdk project --yes tdk resource orders-api --type backend --stack shop --yes tdk up shop Or clone the official starter and open it in any cloud IDE.