DEV Community

Cover image for Local Development Stacks as Code: Reproducibility with TDK CLI
localeostackio
localeostackio

Posted on

Local Development Stacks as Code: Reproducibility with TDK CLI

Local development environments are a recurring source of lost time. Services are started by hand, configuration drifts between machines, and new engineers need days before the system runs. This article describes an approach that treats the local stack as code: each service is described in a manifest, its runtime files are generated from that description, and any hand edits are detected before the stack starts. It uses TDK CLI as the reference implementation and notes where the approach does not apply.

The problem

A typical multi-service system needs a container runtime, a database, a message broker, a gateway, and several application services, each with its own build and start steps. Each service usually carries its own Dockerfile, its own routing rules, and its own file-watching configuration. These files must agree on ports, service names, and start order. When they do not, the failure appears late and far from its cause.

Three consequences follow:

  • Onboarding is slow. A new engineer reproduces the environment by following a document that is often out of date.
  • Configuration drifts. Files edited by hand diverge from the intended design, and the divergence is invisible until something breaks.
  • Review is expensive. Reviewers must check infrastructure files that change for reasons unrelated to the feature under review.

The approach

The approach has three parts.

1. One manifest per service. Each service declares its runtime, its port, and its dependencies in a small structured file. The manifest is the single source of truth for that service.

2. Generated runtime files. A generator derives the Dockerfile, routing configuration, and file-watching configuration from the manifest. Engineers do not write these files, and reviewers do not need to read them to understand intent. Changing a port means changing one value.

3. Drift detection. The generator records a snapshot of each file it writes. Before the stack starts, the current files are compared with the snapshots. A hand edit blocks startup and names the file, so the problem is reported before anything runs.

Together these produce a workflow that is predictable: change the manifest, start the stack, and trust that the runtime files match the description.

How TDK CLI implements it

TDK CLI follows this model for local development.

  • Each service has a service.json. The CLI supports resource types including frontends, backends, workers, MCP servers, and bring-your-own containers.
  • tdk up generates the per-service runtime files under .autogenerated/ and starts the stack with Docker, Traefik, and Tilt. The generated files are described in the repository's generated files reference.
  • Before Tilt starts, tdk up compares the generated files with their saved snapshots. tdk up --dry-run runs the same comparison without starting containers.
  • tdk doctor --json reports whether the host can run the stack at all, including whether a container runtime is reachable.

Two operational details matter for teams. Ports are chosen with a fallback range when the default is taken, and the chosen values are printed on each start. Startup can be limited to named services with tdk up --only, which also starts the services they depend on.

Governance and boundaries

The approach is most useful when its boundaries are explicit.

Local is not production. TDK CLI is designed for the local development loop. The repository states that it does not replace Helm, Argo CD, or Kustomize for cluster deployments, and it does not generate cluster manifests by default. Replicas, probes, ingress, storage, and identity remain the responsibility of the deployment workflow.

Generation is not a substitute for review. Generated files are predictable, but the application code and the decisions in each manifest still need review.

The host must support it. On hosts without a reachable container runtime, the stack cannot start. The doctor command reports this, and no configuration change will fix it.

Migrations have a defined order. The repository's contributor guidance requires new examples to run database migrations in a separate migrator resource, ordered after the database and before the API, and tdk doctor warns when a service runs a migration from its own startup path.

Recommendations for teams

  1. Adopt one manifest per service before adding more services. Retrofitting the convention onto a large stack is harder than starting with it.
  2. Make the dry run part of the review. A reviewer can read the planned start order and services without starting containers.
  3. Keep generated files out of review. Review the manifest and the application code. Treat generated output as a build artifact, checked by drift detection.
  4. Document the host requirements once. Container runtime, operating system, and any opt-in flags belong in the team's onboarding guide.
  5. Keep deployment separate. Use the local stack for development and testing, and use the team's deployment tooling for everything that runs in a shared environment.

Limitations

This approach reduces configuration drift and onboarding time for local development. It does not address production reliability, security review of application code, or capacity planning. Teams should not treat a reproducible local stack as evidence that a system is ready to deploy.

Conclusion

Describing services in manifests, generating their runtime files, and detecting drift before startup gives engineering teams a local environment they can reproduce and review. TDK CLI implements that model for local Docker and Tilt stacks. Its value lies in the boundaries it keeps: local development on one side, and deployment with the team's existing tools on the other.

Top comments (0)