dozeGitHubGet doze
doze

Wakes your stack when you start. Sleeps it when you stop.

Run your whole stack. Feel none of it.

Postgres, Kafka, a whole local AWS and your own services, running natively on your Mac, each awake only while your code is using it. No VM. No port clashes. No setup doc.

Get the doze app
$ brew install --cask doze-dev/tap/doze
Free and open source · macOS on Apple silicon · Linux x86_64 and arm64 · no account, no telemetry

Part of the doze family. Works alone.

The doze app's map of a stack, from the demo stack: who needs whom, and what is awake, dozing or asleep

  The doze app. Close the window and it stays in your menu bar; quit it and your stacks keep running. stand-in from the demo stack · app-rec-request-wakes.webm pending

Your first stack, in the app

Drop a folder. Flip a switch.

Before the tour, the way in: from a fresh install to a running stack in three steps, all in the window.

  1. 1

    Open doze

    The Library is home for every stack on this Mac. On a fresh one it shows three steps and two ways in, and the Machine line offers the one-password setup that gives your services names.

    Drawn from the app's own screen · capture pending
  2. 2

    Drop your repo folder on it

    doze reads the folder’s go.mod and Go imports, package.json, compose files, a Makefile and the variables the code asks for, like DATABASE_URL, and lists what it found and where. Here, Larder’s gateway, before it had a doze.hcl. Nothing starts. Set it up from this repo writes doze.hcl, with the reason above each block.

    Drawn from the app's own screen · capture pending
  3. 3

    Flip Up

    One switch brings the stack up, each service after what it needs; what nobody calls goes to sleep. Next time Up starts what you had up last time. The menu beside it offers Start everything or Choose what starts…: tick the apps you’ll work on, and what they need comes too.

    Drawn from the app's own screen · capture pending

Prefer the terminal? doze init && doze up in the folder does the same, and the app shows the stack either way.

The app, a guided tour

See your whole stack. Touch nothing.

doze is an app first. Eight stops, each a real screen. Scroll inside the frame, use the arrow keys, or pick a stop from the dots.

01 / 08 · Library

Every stack on this machine. A switch each.

Open doze and every project you've brought up is there, running or not. One switch brings a whole stack up; the same switch puts it to bed.

From a terminal dozedoze statusstand-in from the demo stack · app-library.webp pending
The doze app's tiles for one stack, from the demo stack
02 / 08 · Tiles, list, map

One stack, three ways.

Tiles for how each service is doing, a list when there are many, and a map that draws who needs whom. Awake, waking, dozing, asleep: the same four words everywhere.

From a terminal doze statusstand-in from the demo stack · app-overview-tiles / list / map pending
A stack as tiles, from the demo stack: every service with its state, memory and address
03 / 08 · Wake and sleep

Turn one off. What needs it follows.

Switch a database off and doze asks whether the services that need it should sleep too; the memory comes back. Switch one on and it wakes with what it depends on.

From a terminal doze sleep postgresdoze wake catalogstand-in from the demo stack · app-rec-cascade.webm pending
04 / 08 · Inside a service

Its logs, its address, its console.

Logs grouped by run, so this run isn't buried under yesterday's. The connect line, one click to copy. And for a module like Postgres, its own console in the window.

From a terminal doze logs gatewaydoze url postgresstand-in from the demo stack · app-service-gateway.webp pending
The doze app's tiles for one stack, from the demo stack
05 / 08 · ⌘K

⌘K. A verb and a name.

Type “wake cat”, press return. Sleep, wake, restart, pin, logs, copy, open, up, down: every action is a few keys away, across every stack.

From a terminal doze wake catalogstand-in from the demo stack · app-rec-palette.webm pending
06 / 08 · Needs you

What's wrong, with its fix.

A crash, a task that failed, a mistake in doze.hcl: each says what happened and offers the fix. A broken file shows the line and exactly what to write.

stand-in from the demo stack · app-needs-you.webp pending
07 / 08 · Menu bar

Every stack, from the menu bar.

The bat stays in the menu bar after the window closes: each stack and how it's doing, a red dot when something needs you. Quit doze and your stacks keep running.

From a terminal doze updoze downdrawn from the app's own menu · capture pending
08 / 08 · Themes

Five themes, in light and dark.

Violet, teal, burgundy, amber and slate, each light or dark, or following your Mac. A module's console opens in the same light.

drawn from the app's own theme tokens · capture pending
And everything is a command too

Every switch is a command.

So a script, a Makefile or CI can do what you just clicked. Over SSH, with no screen, doze is a dash in your terminal; piped, it prints the status table.

Flip a stack on
doze up
Flip a service on
doze wake catalog
Switch it off, keep its data
doze sleep postgres
Keep it awake through a demo
doze pin postgres
Read what it printed
doze logs gateway
Copy its connect line
doze url postgres
Put the stack to bed
doze down
Hand it to a script, or an agent
doze status --json
~/code/larderour example project
$ doze up
✓ migrate-catalog, migrate-orders … ran
✓ storefront  http://storefront.larder.doze
✓ gateway     http://gateway.larder.doze
○ catalog     asleep, wakes on a connection
○ mailpit     asleep, wakes on a connection

$ psql "$(doze url postgres)"
# its connection string; postgres wakes on the connection

$ doze run -- go test ./...
# the same file runs your tests in CI
One file in your repo

Onboarding is doze up.

doze.hcl says what your app needs; doze.lock pins every engine, byte for byte, so your Postgres is your teammate's Postgres. This is a slice of the one in Larder, a made-up grocery service we built as our example: seven services in Go, Python and TypeScript, a gateway and a web shop.

  • One Postgres, a role and a schema per service
  • Connection strings that wire themselves
  • Migrations that run once, before the code that needs them, and again only when a .sql file changes
  • Kafka topics declared as contracts, not created by whoever writes first
  • Your own domain over https, for when OAuth refuses localhost: doze trust
Read all of Larder's doze.hcl on GitHub →
larder/doze.hcla slice
stack = "larder"

defaults {
  sleep = "2m"
}

module postgres "postgres" {
  version = 18
  role   "catalog" { password = "catalog" }
  schema "catalog" { owner = "catalog" }
}

module kafka "kafka" {
  version            = 4
  auto_create_topics = false
  topic "orders.placed" { partitions = 3 }
}

task "migrate-catalog" {
  run    = "go run ./platform/cmd/migrate services/catalog/migrations"
  inputs = ["services/catalog/migrations/*.sql"]
}

app "catalog" {
  run   = "go run ./services/catalog"
  port  = 8002
  after = [task.migrate-catalog]
  env = {
    DATABASE_URL = "postgres://catalog:catalog@${module.postgres.address}/postgres"
  }
}

Your own domain

Your own domain, over https. One line.

Sign-in providers won’t send you back to localhost subdomains or .test. Cookies, CORS and HSTS care which host they’re on. So put your real dev domain in doze.hcl, run doze trust once, and https://dev.acme.com is your laptop, padlock and every tenant subdomain included. No proxy to configure, no hosts file, no port in the URL.

How your own domain works
app "web" {
  run     = "pnpm dev"
  port    = 3000
  domains = ["dev.acme.com", "*.dev.acme.com"]
}

$ doze trust   # once: one password prompt
  • https://dev.acme.com
  • https://larder.dev.acme.com
  • https://corner.dev.acme.com
Sound familiar?

Nobody designed your local setup. It grew.

Containers, brew services, half-remembered ports and a setup doc that worked once. Here is each small embarrassing truth, and what changes.

npm run dev · make api · ./worker.sh

Which terminal was that?

Seven things, seven ways to start them. doze.hcl says how each one runs, doze up starts them all, and the app keeps each one's logs.

port 5432 is already allocated

Port roulette.

Every service gets a name of its own, like gateway.larder.doze in our example. Two projects can both have their 5432, and you stop keeping ports in your head.

fans: full tilt · battery 31%

The fan.

No VM holding every database all day. Native processes that sleep when nothing calls (after two minutes, in Larder) and wake on the next connection.

“I can't see your running services”

The AI that can't see your machine.

Every command answers in --json: status, logs, the connect line. The agent in your terminal can read your stack instead of guessing at it.

SETUP.md · last updated 2023

Coming back after months.

The setup doc is doze.hcl, and it runs. Clone, doze up, and the map shows the services you've never met and who needs whom.

docker ps -a · 23 exited

Closing the lid with 23 things up.

Quiet services already went to sleep on their own. The menu bar shows what's still awake, and doze down puts a whole stack to bed.

And the big one

Local development didn't get fixed. It got moved.

Teams gave up on local and moved to remote dev environments run by a platform team. Debuggers that won't attach through the tunnel. Port-forwards to juggle. Logs in another tool. And when the dev environment is down, with no ETA and the platform team busy with production, nobody can work. You needed a platform team just to write code, while the most powerful machine you've ever owned sat on your desk.

  • It all runs on your laptop again, natively, one file per repo.
  • A breakpoint just hits. Your debugger attaches to a local process.
  • Nobody's pager is involved. Your stack is down when you closed the lid, not when someone else's cluster is.
  • Platform teams build the platform instead of babysitting dev boxes.
Take the long way round
Polyglot by default

doze runs the command you'd run.

No SDK, no base image, no plugin per language. Each team keeps its toolchain; doze gives every service a name, a port, its connection strings, and sleep.

See all three in Larder →
Gogateway, identity, catalog, orders, paymentsrun = "go run ./services/orders"
Pythonfulfilment and recommendations, with uvrun = "uv run --frozen python -m fulfilment"
TypeScriptnotifications and the storefront, on bunrun = "bun run dev"
Anything elseIf it starts from your terminal, it starts from doze.hcl.run = "[the command you type today]"
Compared

doze or docker compose?

Both are good tools. Containers win at shipping. For the code you write all day, here is what changes.

Questiondozedocker compose
What runsNative processesContainers in a Linux VM
While you're in a meetingAsleepRunning
Memory at rest[measured, named Mac][measured, same Mac]
Your debuggerAttaches to a local processCrosses a VM boundary
Two projects on 5432Fine: each has its own addressRemap one by hand
Three languages in one repoEach runs with its own toolchainA Dockerfile per service
https on your own domainOne line, one password promptA proxy and a CA you set up
Shipping to productionNot its jobImages you can ship
Install

Fans off. Stack on.

doze 0.2 · [0.2 release date] · what's new

Every way to install →
Start here

Get the app

Doze.app for Apple silicon: the window, the bat in your menu bar, and doze on your PATH, always the same version as the app. Open it and the Library walks you through your first stack; its Machine line offers one password prompt, for .doze names.

Download for Mac
$ brew install --cask doze-dev/tap/doze
macOS 14 or later, M1 and newer · signed and notarised · what the first launch changes
Second

Just the command line

For servers, CI and terminal people: the same doze, no window. Install the formula or the cask, not both: they conflict.

$ brew install doze-dev/tap/doze

Linux x86_64 and arm64: Homebrew, mise or a tarball. On Windows, WSL2.

Every way to install

Part of the doze family. Works alone.

Three products, each its own download, each happy on its own. Together, doze runs the other two inside your stack and gives them names.