oxid

oxid

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
oxid — live session
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

From nothing to a live preview.

No YAML novels, no secret juggling. Secrets generate themselves, the wizard does the rest.

01 — run

docker compose up -d

Checksums verified, secrets generated, daemon + Traefik up.

$ curl -fsSL .../install.sh | sh -s -- --docker
[+] Dashboard: http://localhost:8080  → wizard
02 — wizard

Open the dashboard

Five steps, all also scriptable via CLI:

  1. Token
  2. Infra bootstrap — one click
  3. First project — by Git URL
  4. Webhooks
  5. CLI snippet
03 — push

Push a branch

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

Staging environments shouldn't cost like production ones.

The problem

Pay a cloud platform for staging instances asleep 90% of the time, or drown your own server in containers nobody remembers to stop.

The Oxid way

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

Opinionated, so you don't have to configure it.

Zero friction

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.

Real scale-to-zero

Idle branches stop and hand their memory back. The first request starts them again — measured at 285–900 ms.

Resource multiplexing

One shared Postgres, one shared Redis. Dozens of branches, no cluster sprawl.

Fully local, fully audited

No external database. Every deploy and secret access lands in an embedded SQLite log.

One control plane, many surfaces

CLI, TUI, dashboard, desktop app — same daemon, pick whichever fits the moment.

A team, not a shared password

Give each person a role, a scope and an expiry: viewer reads, developer deploys, maintainer owns the secrets. Access model.

Written in Rust

One static binary, tiny footprint, unsafe forbidden workspace-wide.

// the golden path

You never feel the machinery.

  1. 01

    Install

    One command. Secrets generate themselves, printed once.

  2. 02

    Wizard

    Token → infra bootstrap → register by Git URL. First deploy kicks off for you.

  3. 03

    Push a branch

    That's the whole interface from then on. Auto-routed at branch.your-domain.

  4. 04

    Breathe

    Idle → stopped, RAM back. Next visit → awake in under a second.

deploy

The push button, plus an undo.

  • oxid up <branch>
  • oxid rollback [--to <sha>]
  • oxid pause / wake
  • oxid down · rm-project

observe

What's live, what it's doing, what happened.

  • oxid status --sort --filter
  • oxid logs -f — live stream
  • oxid audit --since --kind
  • oxid ps · queue

secrets & policy

Scoped variables, lifetimes, many daemons.

  • oxid env set K=V --scope branch
  • oxid configure --pause-after 30m
  • oxid context add staging --api ...
  • oxid env list / delete

operate

Day-2 care, built in from day one.

  • oxid doctor
  • oxid backup / restore
  • oxid rotate-key · token
  • oxid infra setup

Every command speaks --json with distinct exit codes for CI pipelines, and your shell gets tab completion via oxid completions zsh.

// benchmarks

Measured on real hardware, not asserted.

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.

The rig

CPU
AMD Ryzen 5 3600 — 6 cores / 12 threads, 4.2 GHz boost
Memory
16 GB
Storage
NVMe SSD, btrfs — not a RAM disk
OS
Linux 7.2, Docker Engine 29.7.2
Build
Rust 1.98 — debug profile for the HTTP figures, the released static musl binary for the deploy one

The database

Rows
6,000 environments · 30,000 audit events · 40 projects
Why that size
A node keeps one row per deploy as history, so this is a team a few months in — not an empty install
Engine
Embedded SQLite, WAL, pool of 8
Load
Keep-alive HTTP, so the numbers measure the daemon and not connection setup

Deploy throughput — fifteen branches pushed at once

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.

ScenarioBeforeAfterRuns
15 simultaneous pushes27.3 s7.1 s23.3 / 28.3 / 27.3 → 7.1 / 7.1 / 8.13.8×
…with OXID_DEPLOY_CONCURRENCY=16—4.2 smedian of 126.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.

Heartbeat — the busiest path in the system

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 callersBeforeAfterp50 latency
1242 req/s848 req/s3.9ms → 0.9ms3.5×
8288 req/s3,382 req/s26.8ms → 2.0ms11.7×
32236 req/s4,763 req/s125.5ms → 5.7ms20.2×
64262 req/s4,592 req/s233.7ms → 11.7ms17.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.

Authenticated read — the dashboard and oxid status

Concurrent callersBeforeAfterp50 latency
1283 req/s270 req/s3.2ms → 3.4msunchanged
8356 req/s1,044 req/s22.0ms → 7.1ms2.9×
32366 req/s1,267 req/s85.9ms → 24.2ms3.5×

A single caller has nothing to overlap with, so it gains nothing — and that row is left in rather than dropped.

Where the speed came from

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:

Index the hot column, and stop writing on every request

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×

Open the database as a pool

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

Ready to run. Honest about what's next.

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.

Available

CLI

Full lifecycle from one binary: deploy, rollback, logs -f, scoped secrets, backup/restore, contexts, --json everywhere.

Planned

TUI

Keyboard-driven, branch tree, live logs.

Available

Web dashboard

Embedded in the binary, brutalist by design.

Planned

Desktop app

Tauri tray app for one-click access to preview URLs.

Follow the build. Shape it.

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.