docker compose up -d
Checksums verified, secrets generated, daemon + Traefik up.
$ curl -fsSL .../install.sh | sh -s -- --docker
[+] Dashboard: http://localhost:8080 → wizard
Every branch comes alive.
Push → live preview. Idle → scales to zero, RAM back.
$ curl -fsSL https://raw.githubusercontent.com/sazardev/oxid/main/install.sh | sh -s -- --docker
git push origin feature-login
Building image (cache hit: 85%) ...
https://feature-login.local.dev
oxid pause feature-login
paused — RAM returned
curl feature-login.local.dev
woken in 340ms — serving
// install — 60 seconds
No YAML novels, no secret juggling. Secrets generate themselves, the wizard does the rest.
docker compose up -dChecksums verified, secrets generated, daemon + Traefik up.
$ curl -fsSL .../install.sh | sh -s -- --docker
[+] Dashboard: http://localhost:8080 → wizard
Five steps, all also scriptable via CLI:
That's the whole interface from here on.
$ git push origin feature-carrito
[>] Building (cache hit: 85%) ...
[+] https://feature-carrito.local.dev
Traefik mode: stable subdomains + wake-on-request. Direct-publish: a host port each, no DNS. Install & setup · For developers · Stacks · Env vars · Security
// why
Pay a cloud platform for staging instances asleep 90% of the time, or drown your own server in containers nobody remembers to stop.
Push → deployed → routed. Idle 30 min → stopped, RAM returned. Next visit → awake in under a second. Vercel-style URLs, calculator-sized bill.
// what it offers
No Dockerfile needed — Oxid reads your package.json, go.mod or Cargo.toml and builds one. Commit your own, or an oxid.toml, to take over. 23 stacks.
Idle branches stop and hand their memory back. The first request starts them again — measured at 285–900 ms.
One shared Postgres, one shared Redis. Dozens of branches, no cluster sprawl.
No external database. Every deploy and secret access lands in an embedded SQLite log.
CLI, TUI, dashboard, desktop app — same daemon, pick whichever fits the moment.
Give each person a role, a scope and an expiry: viewer reads, developer deploys, maintainer owns the secrets. Access model.
One static binary, tiny footprint, unsafe forbidden workspace-wide.
// the golden path
One command. Secrets generate themselves, printed once.
Token → infra bootstrap → register by Git URL. First deploy kicks off for you.
That's the whole interface from then on. Auto-routed at branch.your-domain.
Idle → stopped, RAM back. Next visit → awake in under a second.
The push button, plus an undo.
oxid up <branch>oxid rollback [--to <sha>]oxid pause / wakeoxid down · rm-projectWhat's live, what it's doing, what happened.
oxid status --sort --filteroxid logs -f — live streamoxid audit --since --kindoxid ps · queueScoped variables, lifetimes, many daemons.
oxid env set K=V --scope branchoxid configure --pause-after 30moxid context add staging --api ...oxid env list / deleteDay-2 care, built in from day one.
oxid doctoroxid backup / restoreoxid rotate-key · tokenoxid infra setup
Every command speaks --json with distinct exit codes for CI pipelines, and
your shell gets tab completion via oxid completions zsh.
// benchmarks
Every number below comes from the same box running the same load, once against the commit before the work and once against the commit after — two real binaries, not a flag. Medians of five runs after a warmup — three for the deploy figure, which costs a minute a run — with each daemon measured alone so they never competed for CPU.
A team starting its morning: fifteen simultaneous signed GitHub webhooks at one node,
the full Docker and Traefik stack, timed from the first webhook to the last environment
leaving building. All fifteen came up in every run.
| Scenario | Before | After | Runs | |
|---|---|---|---|---|
| 15 simultaneous pushes | 27.3 s | 7.1 s | 23.3 / 28.3 / 27.3 → 7.1 / 7.1 / 8.1 | 3.8× |
…with OXID_DEPLOY_CONCURRENCY=16 | — | 4.2 s | median of 12 | 6.5× |
Three things were in the way, each hiding the next. Every deploy on the node held one
mutex, though sibling branches share no checkout, no container name and no environment
row. A burst then waited out a whole scheduler tick doing nothing, because the pushes
landed after the drain had already read the queue. And with those gone, every deploy
was still doing its own git fetch, one at a time — fourteen deploys
starting in the same millisecond and finishing 425 ms apart, which is exactly what
a fetch to GitHub costs from this machine. One fetch brings down every branch, so they
share it now.
Read the figure as a floor, and note which way: the Docker layer cache was warm here, so each build is a second or two. On cold builds the gap is larger, because what overlaps is then whole builds rather than the bookkeeping around them.
The proxy calls it on every HTTP request to every environment to keep idle detection honest. It resolves a hostname to its environment and records the visit.
| Concurrent callers | Before | After | p50 latency | |
|---|---|---|---|---|
| 1 | 242 req/s | 848 req/s | 3.9ms → 0.9ms | 3.5× |
| 8 | 288 req/s | 3,382 req/s | 26.8ms → 2.0ms | 11.7× |
| 32 | 236 req/s | 4,763 req/s | 125.5ms → 5.7ms | 20.2× |
| 64 | 262 req/s | 4,592 req/s | 233.7ms → 11.7ms | 17.5× |
Throughput used to be flat from 1 to 64 callers while latency climbed in lockstep — the shape of one serialized resource rather than a busy one. It now scales.
oxid status| Concurrent callers | Before | After | p50 latency | |
|---|---|---|---|---|
| 1 | 283 req/s | 270 req/s | 3.2ms → 3.4ms | unchanged |
| 8 | 356 req/s | 1,044 req/s | 22.0ms → 7.1ms | 2.9× |
| 32 | 366 req/s | 1,267 req/s | 85.9ms → 24.2ms | 3.5× |
A single caller has nothing to overlap with, so it gains nothing — and that row is left in rather than dropped.
Three changes shipped, and they separate into two groups that can be measured apart — by re-running the new build with the pool forced back to a single connection. At 32 concurrent callers:
The hostname lookup was scanning a table that only grows, and the visit timestamp — which feeds a decision measured in minutes — was persisted on every single request. These two ship together and were measured together; the split between them is not something this experiment can tell you.
236 → 1,651 req/s · 7.0×
WAL lets readers and the writer proceed at once, so queries overlap instead of queueing behind one connection. Measured by re-running the same build with the pool forced back to a single connection.
1,651 → 4,763 req/s · 2.9×
Reads tell the mirror-image story: the index and the write change do nothing for them (366 → 387 req/s), and the entire 3.5× is the pool.
Under the same load, 64 concurrent writers completed 4,800 writes with no lock contention errors. The box was not idle — it was running its owner's usual containers throughout, for both halves of every comparison. Full method and fixture in BENCHMARKS.md.
// status
Oxid already runs end to end in production: one-command Docker install, webhooks, secret injection, scale-to-zero, zero-downtime redeploys. CLI and dashboard cover the full lifecycle today. TUI and desktop app are next — the ROADMAP tracks exactly what's real versus what's ahead.
Full lifecycle from one binary: deploy, rollback, logs -f, scoped secrets, backup/restore, contexts, --json everywhere.
Keyboard-driven, branch tree, live logs.
Embedded in the binary, brutalist by design.
Tauri tray app for one-click access to preview URLs.
Oxid is fully open source under a no-strings 0BSD license. If the idea resonates, starring the repo helps it reach more people, and issues tagged for contribution are the fastest way in.