Skip to content

Your machine is powerful. Development should feel like it.

doze is docker-compose for local development, without the virtualization. Run your microservices and the engines behind them — real, unmodified Postgres, Valkey, a Mongo-compatible store, local S3/SQS/SNS — as native processes on your machine. The moment a VM enters the picture your fans spin up, your RAM is spoken for, and your debugger is on the wrong side of a boundary. doze keeps everything a process: declared in one file, started in order, ready when you need it, quiet when you don't.
doze.hcl
# doze.hcl — your whole local backend, declared
postgres "app" { version = 18 }
valkey "cache" { version = 9 }
s3 "uploads" {
port = 9000
versioning = true
}
terminal
$ brew install doze-dev/tap/doze
$ doze up
✓ app (postgres 18) ready ✓ cache (valkey 9) ready ✓ uploads (s3) ready
$ doze status
● app postgres 18 active 127.0.0.1:5432 42.5M
○ cache valkey 9 asleep — — # zero cost until used

Containerization is great — for shipping. For development, convenience made it acceptable to be resource-hungry: a VM holding half your RAM all day, the whole stack running while you edit one service, remote-k8s dev loops with latency where your inner loop should be. doze reverses that bargain.

Your fans stay off

No VM, no reserved slab of RAM. At rest, your entire backend is one ~15 MB daemon; engines boot on first connection and reap to zero when idle. The machine’s power goes to your build, not to virtualization.

Your debugger just attaches

Everything is a native process on your kernel. dlv/lldb attach directly, ps/lsof/Instruments see the real engine, core dumps land on your disk. No boundary, no port-forward mazes, no bind-mount heisenbugs. The debugger story →

Real engines, exactly

The actual upstream Postgres 18, the actual Valkey — not images, not emulations. Every extension and wire feature behaves like production, pinned to the byte in doze.lock for your whole team.

Modern apps aren’t one process; they’re a mesh of them. doze runs your services too — declare an API, a worker, an auth service as process blocks and doze starts them in dependency order, waits until each is actually ready, and wires them to each other and to their data. It’s a local microservices orchestrator that happens to also run your databases.

And you can keep the data elsewhere: point a service’s env at a remote or shared Postgres and doze becomes a pure process runner — your code local and debuggable, your data wherever it already lives.

process "api" {
cwd = "../api"
command = "npm run dev"
port = 8080
env = { DATABASE_URL = postgres.db.url } # or a remote URL — doze doesn't mind
health { http = "http://localhost:8080/health"; retries = 30 }
}

Running your own services →

Understand it

The full case: native processes vs containers, why HCL, and an honest look at the alternatives. Why doze →

Build a module

Every engine is a plugin, and third-party modules are exactly as capable as official ones. Start from a working template. Author guide →

Run a registry

Modules arrive signed, key-pinned, and locked — and the registry is static files you can host yourself, air-gapped included. Operator guide → · Browse modules →