oxid/ docs

// cli

Global options

Every command accepts these flags. Resolution order for both values is: flag > environment variable > active context > built-in default.

FlagEnvDefaultDescription
--api <url> OXID_API http://127.0.0.1:8080 Daemon base URL.
--token <t> OXID_TOKEN — Bearer token for daemons configured with OXID_API_TOKEN. Sent as Authorization: Bearer … on every request.
--json — off Machine-readable JSON on stdout; errors stay as plain text on stderr. Pair with exit codes (below) for scripts and CI.
--lang <en|es> OXID_LANG shell locale, else en Language for the CLI's own messages. Falls back to LC_ALL/LC_MESSAGES/LANG. Never affects --json, which scripts parse.

Contexts (oxid context) persist both values per named daemon at ~/.config/oxid/config.toml, kubectl config-style.

Connecting to a server

The short path. oxid context below is the same configuration with more knobs; these are the four commands a person actually types.

oxid login [url]

oxid login http://oxid.example.local:8080
# token is read from stdin — not from your shell history or `ps`

oxid login --as-name staging http://staging.example.local:8080
oxid login                    # log back in to the last server, no URL needed
oxid login --token "$TOKEN" http://…   # for scripts

Saves the server and token, verifies both against the daemon, and makes it active. Verifying is the point of doing this rather than writing a config file: a token typed one character short otherwise fails later, on some unrelated command, with a 401 that looks like the server's fault.

oxid logout

oxid logout            # clears the token, keeps the server
oxid logout --forget   # removes the server entry too
oxid logout --server staging

Clearing the credential and forgetting the address are different things. On a shared machine you want the first, and having to re-type a URL afterwards is exactly the friction that stops people logging out at all.

oxid server · oxid connect <name>

$ oxid server
     NAME             API                        TOKEN
*    default          http://oxid.example:8080   ••••••••cd91
     staging          http://staging.example:8080 ••••••••7a02

$ oxid connect staging

oxid whoami

$ oxid whoami
[+] juan on http://oxid.example.local:8080 (developer)
[~] Projects: 1 — every other project answers 404
[~] Access expires: 2026-11-29 15:36:04
[~] You can: view environments and logs · deploy, pause, wake, destroy

Your name, role, scope, expiry and the list of what you may actually do — derived from the role rather than written out separately, so it cannot drift from what the daemon enforces. Backed by GET /api/v1/me, which needs no capability beyond being authenticated: a credential asking about itself reveals nothing it does not already hold.

Lifecycle

oxid up <branch> [--repo <url> --git-token <pat>]

Registers the repository (idempotent) and deploys branch: clone → build → inject secrets → run → route. Without --repo, registers the current directory (how a locally-run daemon sees your checkout). With --repo https://… the daemon clones the URL itself — how you register against a containerized daemon. Scp-style git@host:org/repo.git is normalized server-side; --git-token is stored encrypted for private repos. If the host is at capacity, the deploy is queued and retried automatically.

oxid up feature-login
[+] Environment live at: http://127.0.0.1:41253/
# against a containerized daemon:
oxid up main --repo https://github.com/you/app.git
oxid up main --repo https://github.com/you/private.git --git-token ghp_xxx

oxid rollback <branch> [--to <sha>]

Redeploys a branch at a prior commit. Without --to, rolls back to the deploy immediately before the current live one.

oxid pause <branch> · oxid wake <branch>

Manual scale-to-zero. pause suspends the container and returns its RAM; wake brings it back. With Traefik configured, the first HTTP request also wakes the environment on its own.

oxid down <branch> [--force] [--purge-secrets]

Destroys a branch's environment permanently. Asks for confirmation unless --force. Branch secrets are kept by default so a recurring branch's config survives a later redeploy; --purge-secrets removes them too.

OptionDescription
--forceSkip the confirmation prompt.
--purge-secretsAlso delete this branch's scoped secrets.

oxid rm-project [--force]

Deletes the project registered for the current directory — every environment, its secrets and the daemon's git cache for the repo.

Observation

oxid status [--sort key] [--filter text]

Environments for the current project: one row per branch, latest deploy wins.

OptionDescription
--sort branch|state|updatedRow order. Default is server order.
--filter <text>Keep rows whose state matches exactly or whose branch contains the text (case-insensitive).

With more than one node registered, a NODE column says where each branch actually runs — the first question anyone asks on a fleet. A single-node install gets exactly the table it always had, and --json carries node_id either way. A project-scoped token, which is refused the node-wide routes, simply does not get the column.

oxid ps [--sort] [--filter]

Every registered project across the daemon. Same flags, applied to project names.

oxid logs <branch> [-f]

Container logs for a branch. One-shot snapshot by default; -f attaches to the daemon's SSE stream and prints lines as they're written — a real live tail, not polling. Ctrl+C detaches without touching the container.

oxid logs -f feature-login
listening on 0.0.0.0:3000 ...

oxid audit [branch] [filters]

The audit trail — every deploy, pause, wake and destroy, with operator and timing. With branch: the full history of that one environment. Without: the most recent events across every project.

OptionDescription
--limit <n>Max events without a positional branch (default 50).
--project <id>Only this project id.
--branch <name>Server-side branch filter for the cross-project feed.
--since / --until <rfc3339>Time window, e.g. 2026-08-01T00:00:00Z.
--kind <kind>Event type: deploy, pause, wake, destroy, …

oxid queue

Deploys waiting for host capacity, oldest first. Non-empty means admission control is doing its job, not that something is broken.

oxid stats

What the node has and what it is using: CPUs and memory as Docker reports them, plus a count per environment state. The same numbers the dashboard's top strip shows. With more than one node registered, a per-node breakdown comes with it, plus a verdict: a node that stopped answering is called out by name with how many environments are stranded on it, rather than left as a row to notice. oxid doctor shows the same section.

Fleet

Oxid runs environments on more than one machine — see Multi-node for how it fits together and what it costs. These commands are node-wide, so a project-scoped token is refused.

oxid node add <name> <endpoint>

Registers a node, or corrects one already registered under that name. The connection is made and probed before anything is written, so a bad endpoint or a missing certificate fails here rather than hours later on somebody else's push.

oxid node add eu-1 tcp://10.0.0.4:2376 \
  --address 10.0.0.4 \
  --tls-ca   /etc/oxid/nodes/eu-1/ca.pem \
  --tls-cert /etc/oxid/nodes/eu-1/cert.pem \
  --tls-key  /etc/oxid/nodes/eu-1/key.pem
OptionDescription
--address <host>Where the control plane dials the ports this node publishes — not the same thing as the endpoint, which is where the Docker API lives. Omit it and Oxid dials its own loopback, so the branch deploys and is unreachable.
--tls-ca / --tls-cert / --tls-keyPaths on the daemon's disk. All three or none. A remote endpoint without them is refused unless OXID_ALLOW_INSECURE_NODES=1.
--reserved-memory-mb <mb>Memory this machine owes its own OS and daemons, overriding OXID_RESERVED_MEMORY_MB for this node alone.

oxid node ls [--envs]

The fleet, with each node's state, memory as its Docker reports it, and how many live environments it holds. A node listed active but not connected is one whose row is fine and whose connection is not — it is called out explicitly rather than left to be inferred from an empty memory column.

--envs lists what each node is actually running, across every project. oxid status is per repository and always was, so this is the only way to answer "what is on this machine" before emptying it. --json carries the same join, so a script does not have to make it.

oxid node drain <node> [--evacuate] · oxid node activate <node>

drain stops new environments landing on a node and touches nothing already running. --evacuate additionally moves every live branch off, one redeploy each through the ordinary zero-downtime path — build on the new node, wait for it to accept connections, cut the proxy over, then remove the old container. Each branch is rebuilt at the commit it is running, never at its current head. A branch that will not build stays where it is and is named, so a half-emptied node is visible rather than silent.

An evacuation is a full rebuild per branch, since images are not distributed. On a busy node that is minutes, which is why it is opt-in.

oxid node rm <node> [--force]

Retires a node. Refused while any environment still points at it, destroyed ones included — the audit trail hangs off environment rows, so freeing the node would delete that history as a side effect of an unrelated command.

Secrets & configuration

oxid env set|list|delete

Environment variables with three resolution scopes — global, project, branch — resolved Global → Project → Branch at container start. Values are encrypted at rest (AES-GCM) and never shown back by list: names and scopes only.

oxid env set DATABASE_URL=postgres://... --scope project
oxid env set STRIPE_API_KEY=sk_... --scope branch --branch feature-login
oxid env list --scope project
oxid env delete STRIPE_API_KEY --scope branch --branch feature-login
OptionDescription
--scope global|project|branchScope of the operation (default global).
--project <id>Project id; auto-registered from the current directory when omitted.
--branch <name>Required when --scope branch.

oxid configure [--pause-after d] [--destroy-after d] [--git-token t]

Changes the current project's lifetime policy and private-repo access. oxid.toml only ever seeds these at first registration — later changes go through here and take effect on the next GC sweep, no redeploy.

OptionDescription
--pause-afterIdle timeout before scale-to-zero pause, e.g. 45m.
--destroy-afterMax lifetime before permanent teardown, e.g. 3d.
--git-tokenPAT for a private repository; empty string clears it. Write-only — never echoed back.

oxid context add|use|list|current|remove

Named daemon contexts, persisted at ~/.config/oxid/config.toml. Switch once, stop repeating --api/--token. Purely local — no daemon round-trip.

oxid context add staging --api https://oxid.internal:8080 --token eyJ...
oxid context use staging
oxid context list          # tokens masked to their last 4 chars
oxid context remove staging --force

Operations

oxid doctor

Health check: daemon reachability, version, latency, and whether the configured token authenticates. The first thing to run when anything feels off.

oxid backup <file> · oxid restore <file>

Consistent snapshot of the daemon's database + secret key as a .tar. Restore stages the upload and applies it on the daemon's next restart — the live database is never touched in place. Restoring requires OXID_ALLOW_RESTORE=1 on the daemon (see Daemon).

oxid rotate-key

Rotates the master encryption key, re-encrypting every secret under a fresh one with zero downtime. Requires the master OXID_API_TOKEN.

oxid token create|list|revoke

Named API tokens for handing out access without sharing the master credential. Repeatable --project <id> scopes a token to those projects only — every other project answers 404, and node-wide operations (stats, infra, backups, token management) are rejected. Unscoped tokens keep full access; audit events attribute actions to the token's name. create prints the raw token once — it is never retrievable again. Requires the master token.

oxid token create ci-bot --project 1 --project 3
oxid token list
oxid token revoke 3

oxid infra status · oxid infra setup

Inspect/bootstrap the Docker network + built-in Traefik container that real wake-on-request scale-to-zero needs. setup is idempotent — an already-satisfied step is left untouched, safe to re-run.

oxid completions <shell>

Prints a completion script to stdout, for zsh, bash, fish and friends.

oxid completions zsh > ~/.zfunc/_oxid

Exit codes

Scripts can branch on the failure kind instead of parsing stderr. With --json, successful output is a single JSON value on stdout.

CodeMeaning
0Success.
1Generic failure (bad input, daemon rejected the request).
2Not found — e.g. no environment for the given branch.
3Daemon unreachable (connection refused, DNS, timeout).
4Unauthorized — missing/invalid token (401/403).