NDL: Production Primitives for Local Development
Local development is where distributed systems often lose validation.
Today’s “app” is rarely one process. It’s a distributed system: one user request can fan out across services, databases, queues, gateways, and configuration—changing independently and failing partially.
Here’s the easy miss: locally, you call services directly on localhost, so everything “works” with root paths, permissive headers, and whatever happens to be in your .env.
In production, that same request goes through a gateway that rewrites a base path, injects and filters headers (like auth and request IDs), and routes to backends discovered by service name (not ports). Suddenly you get a 404 because the service never handled the prefixed route, a 401 because the expected header isn’t there on internal calls, or a timeout because a default localhost URL slipped into config.
Nothing about your business logic changed—you just discovered you were validating a different system.
That’s why local dev can feel like a different job:
- ports, proxies, and mystery URLs
- startup order and dependency readiness
- configuration scattered across files, repos, and machines
- “works on my machine” onboarding
- failures that only reproduce later (CI/pre-production)
NDL is a local development platform designed to close that gap by bringing production primitives down to your laptop—starting with DNS-based service names, gateway routing, service discovery, and centralized configuration.
Not by running Kubernetes locally. By taking the architectural ideas that make Kubernetes powerful—declarative intent, reconciliation, and operator ownership—and applying them to a laptop platform.
The goal isn’t to recreate production perfectly. It’s to bring the primitives—and the app patterns behind them (think 12-Factor App practices like config and environment parity)—into the inner loop, so local environments are repeatable and teams spend less time re-wiring the same system.
For engineering leaders: this is about reducing onboarding friction, shrinking the “integration surprise” window, and improving production readiness by making the inner loop more reliable across teams—so behavior stays predictable as code moves toward production (through CI and pre-production)—without asking everyone to run a cluster.
The Problem: Local Dev Is Missing the Primitives
Production environments have a set of capabilities most teams take for granted:
- service names that resolve
- a gateway/ingress layer that routes traffic consistently
- a source of truth for configuration
- health checks and automation
- observability you can rely on (logs, metrics, traces)
- a way for services to find each other without hardcoded URLs
Local development often replaces those primitives with ad-hoc equivalents:
- “pick a free port and hope nobody else uses it”
- “export this env var and run these three commands in the right order”
- “here’s a README with 20 steps (and two of them are outdated)”
- “debug across five terminals and a handful of logs”
- “watch console output and guess” (no consistent request IDs or cross-service traces)
The outcome is predictable: local environments drift, onboarding becomes tribal knowledge, and integration bugs surface late.
This isn’t a moral failing or a tooling fad. It’s what happens when we build distributed systems without distributed-system primitives in the inner loop.
NDL’s goal is to make those primitives available locally in a way that’s repeatable and ergonomic.
The Promise: Production Primitives, Locally
NDL’s promise is simple:
Local development that uses production primitives—starting with DNS-based service names, gateway routing, service discovery, centralized configuration—and repeatable orchestration.
In practice, that means you can describe the system you want, apply it, and interact with services by name—without recreating the wiring by hand.
At a high level, the workflow looks like:
ndl start
ndl apply -f service.yaml
ndl status
For developers, the key is that you declare intent. Even a basic service looks like a standard resource:
apiVersion: apps.ndl.nuewframe.io/v1alpha1
kind: Deployment
metadata:
name: my-first-app
namespace: default
spec:
source:
workingDirectory: "."
command: "node server.js"
environment:
PORT: "{{.Service.Port}}"
NODE_ENV: "development"
service:
port: 3000
Show It: Two Demos, Same Core Idea
The fastest way to understand NDL is to see the same inner loop work for both a “hello world” service and a realistic multi-service app.
Demo 1: A Simple Service
Start the platform, apply the service, then hit it by name:
# From ndl/demo/hello-node
ndl start
ndl apply -f manifest.yaml
ndl status
curl -s http://hello-node.default.ndl.test:18400
What this proves isn’t “NDL can run a server.” It’s that the environment gives you a stable, production-like way to reach it:
- no port hunting
- no per-service port hunting (one predictable gateway entrypoint)
- a consistent naming scheme
- routing that behaves like a gateway, not a pile of localhost URLs
Demo 2: A Real App (.NET + DB + Web)
Now do the same thing for a small distributed system: an API, a web frontend, and a database.
# From ndl/demo/todo-app-dotnet
ndl start
ndl apply -f .ndl/manifest.yaml
ndl status
# HTTPS gateway (self-signed cert)
curl -sk https://localhost:18443/todo-app/todo-api/todos
The gateway listens on HTTPS :18443 by default; HTTP :18400 redirects to HTTPS.
Instead of manually scripting startup order (“start DB, then API, then web”), NDL uses declarative intent and reconciliation to converge the environment to the desired state.
Instead of pasting connection strings and URLs across services, the platform provides a consistent set of primitives:
- discovery (so services can find each other)
- routing (so inbound traffic has a clear path)
- configuration management (so config doesn’t drift per machine)
This is the core idea: whether your system is simple or complex, the inner loop should feel the same.
How NDL Works (Without Becoming “Kubernetes on Your Laptop”)
NDL borrows ideas from Kubernetes without requiring a cluster:
- Declarative resources — you describe what should be running, not how to start it.
- Continuous reconciliation — the platform compares desired vs. actual state and corrects drift automatically.
- Operators — each primitive (DNS, gateway, registry) is owned by an operator that reacts to events independently.
The result is event-driven choreography: coordination through events and independent reactions, not one central script trying to orchestrate everything.
These architectural ideas enable an initial set of primitives, and make it straightforward to add more over time.
The Primitives (What You Get Locally)
NDL starts with the primitives that remove the most “glue work” for local distributed development.
DNS-based service names
Stable service names let you stop thinking in ports and start thinking in identities. In NDL, service domains like *.ndl.test resolve for local services (and the domain is configurable).
Gateway routing
Traffic routes through a gateway layer so your local access patterns look more like production: host/path-based routing, consistent entrypoints, and less hardcoded URL sprawl.
Service registry (discovery)
Services register; callers discover. This decouples “who is up” from “who is calling,” which is a core distributed systems pattern that’s often missing locally.
Centralized configuration (optional)
Configuration is part of the environment, not a pile of per-service .env files. This supports 12-factor-style configuration practices and reduces drift across machines.
Observability (optional)
Production systems are debuggable because they emit reliable signals: logs, metrics, and traces. If those signals exist locally, you can follow a request across services the same way you will before production—and in production—instead of relying on scattered console output and guesswork.
Who NDL Is For (And When It’s Not)
NDL is a good fit when:
- you build multi-service apps (or you’re headed there)
- your team feels the pain of wiring, config drift, and late integration failures
- you want a consistent inner loop across stacks (polyglot teams)
- you like Kubernetes ideas but don’t want Kubernetes overhead locally
NDL is not the best fit when:
- you need to mirror the full Kubernetes API surface area locally
- your local dev constraints require a full cluster simulation
In those cases, local Kubernetes is still a solid option—it’s just a different tradeoff.
What’s Next
NDL is being built in the open, and the direction is clear: make production-grade primitives available in the developer inner loop, with a workflow that’s declarative, repeatable, and extensible.
Call to Action: Early Access
If this matches the problems you’re feeling—setup friction, environment drift, and late integration failures—we’d love your input.
Join the waitlist for early access →
When you reach out, let us know what problem you are hoping this tool will help you solve.