DEV Community

Cover image for Everything You Need to Know About Local Development Stacks
localeostackio
localeostackio

Posted on

Everything You Need to Know About Local Development Stacks

Everything You Need to Know About Local Development Stacks

Modern applications are rarely one program. A typical feature touches a web frontend, an API, a couple of backend services, a database, a message broker, and a gateway that routes traffic between them. Running that on a laptop is the local development stack, and getting it right decides how fast your team can change code.

This article explains what a local stack is, why it is hard, the options teams use, what a good setup looks like, and how TDK CLI approaches the problem.

What is a local development stack?

A local development stack is the set of services your application needs, running on your own machine, wired together the way they run in production. It usually includes:

  • Services your code defines (APIs, workers, frontends).
  • Data stores such as PostgreSQL, MySQL, or Redis.
  • Messaging such as NATS, Kafka, or RabbitMQ, when services talk through events.
  • A gateway or router that gives each service a path or hostname.
  • Build and reload tooling that rebuilds and restarts services as you edit code.

The point is not to copy production exactly. It is to reproduce the shape of production closely enough that a feature which works locally also works when deployed.

Why local stacks are hard

A single service is easy to run: install dependencies, start the process, open a port. Add more services and the work grows in several directions at once.

  • Each service has its own runtime. One is Node, one is Go, one is Python. Each needs its own image, dependencies, and start command.
  • Services find each other by address. Hostnames, ports, and environment variables must match across every service.
  • Data must exist before the code runs. Databases need schemas and migrations applied in the right order.
  • Startup order matters. A service that starts before its database is ready fails in confusing ways.
  • Changes ripple. Editing one service's API may break its callers, and you only find out when you run them together.
  • Configuration drifts. The Compose file, the proxy config, and the Tilt file drift apart as the team changes things, and nobody notices until something breaks.

Most teams hit these problems within a few services. The typical responses are all unsatisfying in one way or another.

Common approaches

Run everything by hand. Open five terminals, start each service, and hope the ports match. This works for a week. It does not survive a new hire or a second project.

Hand-written Docker Compose. A docker-compose.yml defines containers, networks, and volumes. It is a solid base, but every service needs its own entry, routing needs its own proxy config, and live reload needs extra work. It grows large and gets stale.

A local Kubernetes cluster. Tools such as kind and minikube run a real cluster on your machine. This matches production closely, but it adds a heavy layer: cluster creation, image loading, manifests, and slow feedback. For most everyday development it is more than the job needs.

Shared development environments. Everyone deploys to one shared dev cluster. This avoids local setup, but developers block each other, changes land in a shared state, and it fails offline.

Mocks and stubs. Replace dependencies with fakes. This is fast and useful for unit tests, but it hides the integration problems that local stacks exist to catch.

None of these is wrong for every team. The question is which cost you are willing to pay.

What a good local stack provides

Whatever tooling you pick, a good local stack should:

  1. Start with one command. A new developer should be able to run the whole system without reading a wiki page.
  2. Reflect production's shape. Same kinds of services, same routing pattern, same data stores.
  3. Reload on change. Editing code should update the running service in seconds, not require a full rebuild.
  4. Keep configuration in one place. Each service declares what it needs, and the tooling derives the rest.
  5. Make generated files inspectable. If the tool writes Dockerfiles or routing config, you can read them and see whether they match what the tool would generate now.
  6. Avoid heavy prerequisites. Everyday work should not require a cluster.
  7. Show what is running. You should be able to see which services are up, which are failing, and why.

How TDK CLI approaches it

TDK CLI is built around the goal of running a local stack without hand-written plumbing. The main idea is one service.json per service. You describe the service, and TDK generates the local runtime files from that description: the Dockerfile, the Traefik routing configuration, and the Tiltfile.

The workflow looks like this:

  1. Describe each service. Each service directory has a service.json that declares its runtime, port, and what it needs.
  2. Start the stack. Run tdk up. Docker runs the containers, Traefik routes traffic between them, and Tilt watches the services.
  3. Edit and see the result. Tilt live-updates containers when files change, so you do not rebuild the stack for each edit.
  4. Inspect the stack. Run tdk ui for an interactive terminal dashboard. Move with the arrow keys or j/k, search with /, and switch tabs with 1 to 5.
  5. Check the generated files. TDK records the files it generates and reports drift, so you can see when local config no longer matches the descriptions.

Two things TDK CLI deliberately does not do. It does not require a local Kubernetes cluster, so there is no kind or minikube to install. And it is not a deployment tool. Production deployment stays with Helm, Argo CD, or Kustomize.

For concrete projects to try, the TDK example repositories include the ERP example, the e-commerce example, and the auth, queue, and email example.

Setting up your first local stack

A practical order, whatever tool you use:

  1. List your services and what each one depends on. Draw the arrows; they become your startup order and routing rules.
  2. Give each service a single way to start in development, with no manual steps.
  3. Add data stores and brokers as services in the same stack, not as separate things to remember.
  4. Route everything through one entry point, so clients use the same addresses locally and in production.
  5. Enable live reload for the services you edit most often.
  6. Write down the one command a new developer runs, and test it on a clean machine.

If a step needs a sentence of explanation in your README, that step is a candidate for automation.

Common mistakes

  • Different ports per developer. Hard-coded ports collide. Use the routing layer so services get stable names.
  • Dependencies started by hand. If developers must start the database first, sometimes they forget. Declare the dependency.
  • Secrets copied from production. Local stacks should use local, disposable credentials.
  • Local-only behavior. If a service works only because of a local shortcut, production will surprise you. Keep the shortcuts visible and few.
  • Stale generated config. Generated files that people edit by hand lose their meaning. Regenerate, and let drift checks catch the difference.
  • No health signal. A container that is running is not the same as a service that is ready. Check readiness, not just process existence.

When a local stack is not enough

A local stack is for development and testing. It does not prove that your system handles production load, failover, or real network conditions. Keep staging environments for integration checks that depend on real infrastructure, and run load tests against environments built for them.

Summary

A local development stack is the bridge between writing code and trusting it in production. Its quality determines how quickly your team sees a change working across services. A good setup starts with one command, reflects production's shape, reloads on change, keeps configuration in one place, and makes generated files easy to inspect. TDK CLI packages those ideas around one service.json per service and a single tdk up command, so the local stack stays in step with the descriptions you wrote.

Top comments (0)