Deployment Models
DagNats supports a single-binary server for simplicity, a self-clustered set of those servers for high availability, and a distributed deployment for scale.
Single Binary (dagnats serve)
The dagnats serve command starts everything in one process: an embedded NATS server, the workflow engine, the API, triggers, and an HTTP server. Zero configuration required.
dagnats serveThis is the recommended model for development, staging, and small-to-medium production workloads. The embedded NATS server binds to 127.0.0.1 by default, so only local connections are accepted.
What runs inside dagnats serve:
| Component | Role |
|---|---|
| Embedded NATS | JetStream streams, KV buckets, pub/sub |
| ActorOrchestrator | Per-workflow actors, event processing |
| API Service | REST + NATS micro control plane |
| TriggerService | Cron, subject, and webhook triggers |
| HTTP Server | REST endpoints, health checks, webhooks |
| Telemetry | Tracing, metrics, structured logging |
Your workers are separate processes. They connect to the embedded NATS server over the network. The only handlers that run inside dagnats serve are the config-driven exec and http workers declared in dagnats.yaml.
Startup Order
- Resolve data directory (platform default if unset, created if missing)
- Start embedded NATS server
- Connect internal client to
nats://localhost:{port} - Create all JetStream streams and KV buckets (
natsutil.SetupAll), within a 30-second budget standalone or 3 minutes in a cluster - Initialize telemetry pipeline
- Start API service, actor orchestrator, HTTP bridge, trigger service
- Start HTTP server
- Block on SIGINT/SIGTERM
Shutdown Order
Shutdown reverses startup with a 15-second deadline:
- HTTP server graceful shutdown (5s timeout)
- Stop the config watcher, triggers, the token store and the NATS API service
- Stop embedded config-driven workers, so in-flight tasks can publish completion events
- Stop orchestrator (unsubscribe from history stream, stop all actors)
- Stop the metrics and queue snapshot publishers, then flush telemetry
- Drain NATS connection (flush pending messages)
- Stop embedded NATS server
If shutdown has not finished when the deadline passes, dagnats serve returns a shutdown timeout error instead of waiting.
Self-Clustered Deployment
Three or five dagnats serve nodes can form their own NATS cluster, with JetStream replicating state across them. There is no external NATS to run. See Clustering.
Distributed Deployment
For larger workloads or multi-machine setups, run components as separate binaries connecting to an external NATS cluster. dagnats-engine and dagnats-api read the server URL from DAGNATS_NATS_URL (the older NATS_URL still works), defaulting to nats://127.0.0.1:4222:
# Machine 1: NATS cluster (or use NATS Cloud)
nats-server -js -c nats.conf
# Machine 2: Engine + API
DAGNATS_NATS_URL=nats://nats-host:4222 dagnats-engine
DAGNATS_NATS_URL=nats://nats-host:4222 dagnats-api
# Machine 3+: Workers (your own binaries, connecting with the worker SDK)In this model, you manage the NATS infrastructure yourself. Each component connects to the external NATS server. The engine, API, and workers can scale independently. dagnats-api listens on DAGNATS_LISTEN_ADDR (default :8080) and serves the REST API only: /health, /ready and /health/cluster belong to dagnats serve. dagnats-engine and dagnats-api run SetupAll on start, which creates every stream and bucket the engine, API, triggers and workers use. They read DAGNATS_NATS_JETSTREAM_REPLICAS (0, 1, 3 or 5) the way dagnats serve does. With 0 or unset, they derive the factor from the cluster they connect to: 5 when it can place five replicas, otherwise 3 (retried for about 40 seconds while a booting cluster refuses; startup fails if it never can), and 1 outside a cluster. Provisioning never lowers the replica count of a stream or bucket that already exists; it keeps the higher count and logs a warning.
Leaf Node Topology
The embedded NATS server in dagnats serve can connect to a hub cluster as a leaf node. This gives you single-binary simplicity with multi-cluster reach.
# dagnats.yaml
leaf_remotes: nats://hub1:7422, nats://hub2:7422When leaf remotes are configured:
- The embedded NATS server binds to
0.0.0.0(instead of127.0.0.1) for hub communication - NATS handles message routing transparently between the leaf and hub
- All internal components still connect to
localhost:{port} - Workers can connect to either the leaf or the hub – same code path
Leaf node mode enables geographic distribution, multi-team isolation, or connecting edge deployments to a central cluster. A maximum of 10 leaf remotes can be configured.
When to Use Each Model
| Scenario | Model |
|---|---|
| Development and testing | dagnats serve (standalone) |
| Small production (< 50 workers) | dagnats serve (standalone) |
| Multi-region or multi-team | dagnats serve (leaf node) |
| High availability without external NATS | dagnats serve (self-clustered) |
| High availability with an external NATS cluster | Distributed |
| Independent scaling of engine vs workers | Distributed |
Health Checks
dagnats serve exposes health endpoints on its HTTP server:
| Endpoint | Behavior |
|---|---|
GET /health | 200 if NATS connected and JetStream available, 503 otherwise |
GET /ready | 200 only after all components have started |
GET /health/cluster | Cluster membership and per-stream replica state; {"mode":"standalone","ok":true} without cluster routes. See Clustering |
Use /health for liveness probes and /ready for readiness probes in container orchestrators.
Scaling Workers
Workers scale horizontally by running more instances. Each worker connects to NATS and pulls tasks from JetStream consumers. NATS handles load distribution automatically via pull consumers with MaxAckPending.
# Run 5 instances of the same worker
for i in $(seq 1 5); do
my-worker --nats-url nats://localhost:4222 &
doneThe engine does not need to know how many workers exist. Worker discovery is observability-only via the workers KV bucket (60s TTL heartbeat). The engine never reads it.