CLI Reference: Every bhatti CLI command with synopsis, options, examples, and exit codes. Best starting point for agents driving bhatti from a shell. # CLI Reference > Every bhatti command, global flags, environment variables, output formats, and exit codes. `bhatti` is a single binary. `bhatti serve` runs the daemon; everything else is a CLI client that talks to the daemon’s HTTP API. All sandbox commands accept the sandbox name or ID interchangeably. ```bash bhatti setup # configure endpoint + API key bhatti create --name dev # create a sandbox bhatti exec dev -- echo hello # run a command bhatti shell dev # interactive shell (Ctrl+\ to detach) bhatti destroy dev # clean up ``` ## Operations [Section titled “Operations”](#operations) | Command | Syntax | Description | | -------------------------------------------------------------------- | ------------------------------------------------------------- | ----------------------------------------- | | **Core** | | | | [`create`](/docs/reference/cli/sandbox/create/) | `bhatti create [flags]` | Create a new sandbox VM | | [`destroy`](/docs/reference/cli/sandbox/destroy/) | `bhatti destroy [-y]` | Destroy a sandbox (alias: `rm`) | | [`edit`](/docs/reference/cli/sandbox/edit/) | `bhatti edit [--keep-hot \| --allow-cold]` | Update mutable settings | | [`exec`](/docs/reference/cli/exec/exec/) | `bhatti exec [--] ` | Run a command in a sandbox | | [`inspect`](/docs/reference/cli/sandbox/inspect/) | `bhatti inspect ` | Show sandbox details (alias: `info`) | | [`list`](/docs/reference/cli/sandbox/list/) | `bhatti list [-o wide]` | List sandboxes (alias: `ls`) | | [`ports`](/docs/reference/cli/exec/ports/) | `bhatti ports ` | List listening ports inside a sandbox | | [`ps`](/docs/reference/cli/exec/ps/) | `bhatti ps ` | List active sessions in a sandbox | | [`share`](/docs/reference/cli/networking/share/) | `bhatti share [--revoke]` | Generate a web shell URL | | [`shell`](/docs/reference/cli/exec/shell/) | `bhatti shell [--new]` | Open an interactive shell (alias: `sh`) | | [`start`](/docs/reference/cli/sandbox/start/) | `bhatti start [--force]` | Resume a stopped sandbox | | [`stop`](/docs/reference/cli/sandbox/stop/) | `bhatti stop ` | Snapshot and stop a sandbox | | **Files** | | | | [`file read`](/docs/reference/cli/files/read/) | `bhatti file read ` | Read a file from a sandbox | | [`file write`](/docs/reference/cli/files/write/) | `bhatti file write ` | Write a file (reads from stdin) | | [`file ls`](/docs/reference/cli/files/ls/) | `bhatti file ls ` | List directory contents | | **Networking** | | | | [`publish`](/docs/reference/cli/networking/publish/) | `bhatti publish -p [-a ] [--shell]` | Publish a port with a public URL | | [`unpublish`](/docs/reference/cli/networking/unpublish/) | `bhatti unpublish -p ` | Remove a published port | | **Images** | | | | [`image list`](/docs/reference/cli/images/list/) | `bhatti image list` | List available rootfs images | | [`image pull`](/docs/reference/cli/images/pull/) | `bhatti image pull [--name N] [--auth U:T]` | Pull from a public OCI registry | | [`image import`](/docs/reference/cli/images/import/) | `bhatti image import \| --tar [--name N]` | Import a Docker image or tarball | | [`image save`](/docs/reference/cli/images/save/) | `bhatti image save --name ` | Save a sandbox’s filesystem as an image | | [`image delete`](/docs/reference/cli/images/delete/) | `bhatti image delete [-y]` | Delete an image | | [`image share`](/docs/reference/cli/images/share/) | `bhatti image share --user ...` | Share an image with specific users | | [`image unshare`](/docs/reference/cli/images/unshare/) | `bhatti image unshare --user ...` | Revoke image access from users | | **Volumes** | | | | [`volume create`](/docs/reference/cli/volumes/create/) | `bhatti volume create --name --size ` | Create a persistent volume | | [`volume list`](/docs/reference/cli/volumes/list/) | `bhatti volume list` | List volumes | | [`volume delete`](/docs/reference/cli/volumes/delete/) | `bhatti volume delete [-y]` | Delete a volume | | [`volume resize`](/docs/reference/cli/volumes/resize/) | `bhatti volume resize --size ` | Resize a volume (grow only) | | [`volume clone`](/docs/reference/cli/volumes/clone/) | `bhatti volume clone --name ` | Clone a volume (point-in-time copy) | | [`volume backup`](/docs/reference/cli/volumes/backup/) | `bhatti volume backup ` | Back up a volume to S3 | | [`volume backup-list`](/docs/reference/cli/volumes/backup-list/) | `bhatti volume backup-list ` | List backups for a volume | | [`volume restore`](/docs/reference/cli/volumes/restore/) | `bhatti volume restore --backup-id ` | Restore a volume from a backup | | [`volume backup-delete`](/docs/reference/cli/volumes/backup-delete/) | `bhatti volume backup-delete [-y]` | Delete a volume backup | | **Secrets** | | | | [`secret set`](/docs/reference/cli/secrets/set/) | `bhatti secret set ` | Create or update a secret | | [`secret list`](/docs/reference/cli/secrets/list/) | `bhatti secret list` | List secret names | | [`secret delete`](/docs/reference/cli/secrets/delete/) | `bhatti secret delete [-y]` | Delete a secret | | **Snapshots** | | | | [`snapshot create`](/docs/reference/cli/snapshots/create/) | `bhatti snapshot create --name ` | Checkpoint a running sandbox | | [`snapshot list`](/docs/reference/cli/snapshots/list/) | `bhatti snapshot list` | List snapshots | | [`snapshot resume`](/docs/reference/cli/snapshots/resume/) | `bhatti snapshot resume [--name ]` | Resume from a snapshot into a new sandbox | | [`snapshot delete`](/docs/reference/cli/snapshots/delete/) | `bhatti snapshot delete [-y]` | Delete a snapshot | | **Server & admin** | | | | [`serve`](/docs/reference/cli/admin/serve/) | `bhatti serve` | Start the bhatti daemon | | [`setup`](/docs/reference/cli/admin/setup/) | `bhatti setup` | Configure CLI endpoint and API key | | [`version`](/docs/reference/cli/admin/version/) | `bhatti version` | Print version and check for updates | | [`update`](/docs/reference/cli/admin/update/) | `bhatti update [--cli-only] [--tiers ]` | Update bhatti | | [`completion`](/docs/reference/cli/admin/completion/) | `bhatti completion ` | Generate shell completion script | | [`user create`](/docs/reference/cli/admin/user-create/) | `bhatti user create --name [--max-...]` | Create a user (server-only) | | [`user list`](/docs/reference/cli/admin/user-list/) | `bhatti user list` | List users (server-only) | | [`user rotate-key`](/docs/reference/cli/admin/user-rotate-key/) | `bhatti user rotate-key ` | Rotate a user’s API key (server-only) | | [`user delete`](/docs/reference/cli/admin/user-delete/) | `bhatti user delete [-y]` | Delete a user (server-only) | | [`admin status`](/docs/reference/cli/admin/admin-status/) | `bhatti admin status` | One-shot system overview (server-only) | | [`admin events`](/docs/reference/cli/admin/admin-events/) | `bhatti admin events [--type T] [--since 24h]` | Query the event log (server-only) | | [`admin metrics`](/docs/reference/cli/admin/admin-metrics/) | `bhatti admin metrics [--since 1h]` | Query metrics snapshots (server-only) | ## Global flags [Section titled “Global flags”](#global-flags) These flags are accepted by every command. | Flag | Description | | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | | `--url ` | API endpoint (overrides config and `BHATTI_URL`). | | `--token ` | API key (overrides config and `BHATTI_TOKEN`). | | `--data-dir ` | Path to the server’s data directory. Required for `bhatti user *` and `bhatti admin *` since those operate directly on the local SQLite database. | | `--json` | Print machine-readable JSON instead of the default human format. Universally supported. | | `--timing` | Print a per-request timing breakdown (DNS, connect, TLS, server, transfer, total) to stderr after the command runs. | | `-h`, `--help` | Show help for any command. | ## Environment variables [Section titled “Environment variables”](#environment-variables) | Variable | Used by | Description | | --------------------- | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------- | | `BHATTI_URL` | CLI | API endpoint. Falls back when `--url` is not set and the config file has no `api_url`. | | `BHATTI_TOKEN` | CLI | API key. Same fallback semantics as `BHATTI_URL`. | | `BHATTI_CONFIG` | CLI + server | Override path to the config file. When set, layered loading is bypassed and only this file is read. | | `BHATTI_LOG_LEVEL` | server (`bhatti serve`) | `debug`, `info` (default), `warn`, `error`. | | `BHATTI_FORCE_STREAM` | CLI (`bhatti exec`) | Set to `1` to force NDJSON streaming output even when stdout is not a TTY. Useful in CI logs where you want real-time progress. | ## Configuration precedence [Section titled “Configuration precedence”](#configuration-precedence) Per-call flag → config file → environment variable → built-in default. ```plaintext --url (highest) api_url: (in config file) BHATTI_URL= (env var) http://localhost:8080 (default, lowest) ``` The config file is the primary source — `bhatti setup` writes it and most users never touch the rest. Environment variables are the fallback for CI pipelines and scripts. See [Configuration](/docs/reference/config/) for the file format and layered loading rules (`/etc/bhatti/config.yaml` for system settings, `~/.bhatti/config.yaml` for client credentials). ## Output formats [Section titled “Output formats”](#output-formats) **Human (default).** Compact, TTY-friendly. `bhatti list` prints columns; `bhatti inspect` prints kubectl-`describe`-style details with live disk usage on running VMs. **`--json`.** Universally supported. Drops all human formatting; emits the raw API response (for queries) or a small status object (for mutations). **`bhatti list -o wide`.** Adds CPUs, memory, disk, and image columns to `list`. Equivalent to `kubectl get -o wide`. **Streaming exec output.** `bhatti exec` auto-detects how to format its output: * Stdout is a TTY → NDJSON streaming, line-by-line, flushed immediately. * Stdout is a pipe or `--json` is set → buffered JSON with `{exit_code, stdout, stderr}`. * Override with `BHATTI_FORCE_STREAM=1` to force streaming when piping. ## Exit codes [Section titled “Exit codes”](#exit-codes) | Code | Meaning | | ----- | -------------------------------------------------------------------------------------------------------------------------------- | | `0` | Success. | | `1` | CLI or API error (see stderr for details and recovery hints). | | `` | `bhatti exec` only — the child process’s exit code is forwarded verbatim, so a script’s `$?` reflects the command’s true status. | Destructive commands (`destroy`, `volume delete`, `image delete`, `snapshot delete`, `volume backup-delete`, `secret delete`, `user delete`) require confirmation. Pass `-y` / `--yes` to skip the prompt; without `-y` in a non-interactive shell, the command exits `1`. ## Shell completion [Section titled “Shell completion”](#shell-completion) ```bash bhatti completion bash > /etc/bash_completion.d/bhatti bhatti completion zsh > "${fpath[1]}/_bhatti" bhatti completion fish > ~/.config/fish/completions/bhatti.fish ``` Sandbox-name completion uses a local cache (`/tmp/bhatti-completions-`) updated by `create`, `destroy`, and `list`. It never hits the network — instant, works offline. # Server & admin > Daemon, configuration, version, updates, user management, observability. Commands that run on the bhatti server itself, plus a couple that work from any client (`setup`, `version`, `completion`, `update`). ## Categories [Section titled “Categories”](#categories) | | | | ------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Daemon** | [`serve`](/docs/reference/cli/admin/serve/) | | **Client config** | [`setup`](/docs/reference/cli/admin/setup/), [`version`](/docs/reference/cli/admin/version/), [`completion`](/docs/reference/cli/admin/completion/) | | **Updates** | [`update`](/docs/reference/cli/admin/update/) | | **User management** | [`user create`](/docs/reference/cli/admin/user-create/), [`user list`](/docs/reference/cli/admin/user-list/), [`user rotate-key`](/docs/reference/cli/admin/user-rotate-key/), [`user delete`](/docs/reference/cli/admin/user-delete/) | | **Observability** | [`admin status`](/docs/reference/cli/admin/admin-status/), [`admin events`](/docs/reference/cli/admin/admin-events/), [`admin metrics`](/docs/reference/cli/admin/admin-metrics/) | ## Server-only commands [Section titled “Server-only commands”](#server-only-commands) `user *` and `admin *` operate **directly on the server’s SQLite database** (`/var/lib/bhatti/state.db`). They aren’t HTTP API calls — they need filesystem access to the database. Run them on the server, typically with `sudo`. `serve` runs the daemon. Also server-only by definition (requires KVM and root). The remaining commands (`setup`, `version`, `update`, `completion`) work from any client. ## Quick patterns [Section titled “Quick patterns”](#quick-patterns) ```bash # Bootstrap a new user (server) sudo bhatti user create --name alice --max-sandboxes 10 # Check overall health (server) sudo bhatti admin status # Find recent thermal pauses (server) sudo bhatti admin events --type thermal --since 24h # Update the CLI on a developer machine bhatti update # Update everything on the server (CLI, kernel, rootfs, lohar) sudo bhatti update ``` # bhatti admin events > Query the event log. Filter by type, user, sandbox, time range. # bhatti admin events server only Query the structured event log. ## Synopsis ``` sudo bhatti admin events [--type ] [--user ] [--sandbox ] [--since ] [--limit ] [--count] ``` ## Description Returns rows from the audit/observability event log stored in the local database. Each event captures a moment in the system: sandbox lifecycle changes, thermal transitions, snapshot operations, image pulls, auth failures, proxy errors, daemon start/shutdown, and more. Default output is one row per event with timestamp, type, user, sandbox, and a per-event-type summary line. JSON output includes the full meta object. `--type` is a **prefix match**, so `--type thermal` matches `thermal.pause`, `thermal.wake`, `thermal.snapshot`, etc. `--since` accepts: * Relative durations: `30s`, `15m`, `24h`, `7d`. * Absolute dates: `2026-04-01` (UTC midnight), `2026-04-01T12:00:00`, full RFC3339. `--user` and `--sandbox` accept either the name or the internal ID. Names are easier; IDs are stable across renames. `--count` returns a number-only response — useful for alerting/scripting (`if [[ $(bhatti admin events --type auth.failed --since 5m --count) -gt 10 ]]; then ...`). ## Examples ```bash # Last 50 events of any kind sudo bhatti admin events ``` ```bash # All thermal events in the last day sudo bhatti admin events --type thermal --since 24h ``` ```bash # Auth failures in the last hour, count only sudo bhatti admin events --type auth.failed --since 1h --count ``` ```text 3 ``` ```bash # Everything Alice did this week sudo bhatti admin events --user alice --since 7d ``` ```bash # Drill into one sandbox's history sudo bhatti admin events --sandbox dev --since 24h ``` ```bash # JSON for piping into jq sudo bhatti admin events --type sandbox.destroyed --since 7d --json \ | jq '[.[] | .meta.lifetime_s] | add / length' # avg sandbox lifetime ``` ## Options \| Flag | Default | Description | | ---- | ------- | ----------- | | `--type ` | — | Filter by event type. Prefix match (`thermal` matches `thermal.*`). | | `--user ` | — | Filter by user. Name or user ID. | | `--sandbox ` | — | Filter by sandbox. Name or ID. | | `--since ` | — | Earliest event to include. Relative (`24h`, `7d`) or absolute (`2026-04-01`). | | `--limit ` | `50` | Maximum number of events to return. | | `--count` | `false` | Return only the count of matching events, not the rows. | See [Global flags](/docs/reference/cli/#global-flags) for `--url`, `--token`, `--json`, `--timing`, `--data-dir`. ## See also * [`bhatti admin status`](/docs/reference/cli/admin/admin-status/) — shows the most recent \~10 events inline * [`bhatti admin metrics`](/docs/reference/cli/admin/admin-metrics/) — counter-based view # bhatti admin metrics > Time-bucketed metrics — request rates, errors, thermal counts, host load. # bhatti admin metrics server only Query metrics snapshots over time. ## Synopsis ``` sudo bhatti admin metrics [--since ] ``` ## Description The server takes a metrics snapshot roughly every minute and persists it to the database (in `metrics_snapshots`). This command reads them back and prints a compact table — request rates, error counts, proxy traffic, cold wakes, sandbox state distribution, host load, available memory. The output is **bucketed automatically** based on the time range: \| Range | Bucket size | | ----- | ----------- | | < 2 hours | 1 minute | | 2–24 hours | 15 minutes | | > 24 hours | 1 hour | Each row aggregates the snapshots in its bucket (sums for counters like `req/min`, last-value for gauges like `LOAD`). `--since` accepts the same formats as [`admin events --since`](/docs/reference/cli/admin/admin-events/) — relative durations or absolute dates. The default is **1 hour**. ## Examples ```bash # Last hour at 1-minute resolution (default) sudo bhatti admin metrics ``` ```text TIME REQ/min ERR PROXY/min WAKES HOT WARM COLD LOAD MEM_AVAIL 14:21 47 0 8 0 4 6 7 0.4 19421 MB 14:20 52 1 9 1 4 5 8 0.5 19388 MB 14:19 44 0 7 0 4 4 9 0.5 19412 MB ... ``` ```bash # Last 12 hours, auto-bucketed to 15-minute intervals sudo bhatti admin metrics --since 12h ``` ```bash # Last week, auto-bucketed to 1-hour intervals sudo bhatti admin metrics --since 7d ``` ```bash # Machine-readable, raw 1-min snapshots for the last 30 minutes sudo bhatti admin metrics --since 30m --json | jq '.[] | .api_requests' ``` ## Options \| Flag | Default | Description | | ---- | ------- | ----------- | | `--since ` | `1h` | Earliest snapshot to include. | See [Global flags](/docs/reference/cli/#global-flags) for `--url`, `--token`, `--json`, `--timing`, `--data-dir`. ## See also * [`bhatti admin status`](/docs/reference/cli/admin/admin-status/) — current totals * [`bhatti admin events`](/docs/reference/cli/admin/admin-events/) — what happened, when # bhatti admin status > One-shot system overview — sandboxes, users, host load, recent events. # bhatti admin status server only One-shot system overview. ## Synopsis ``` sudo bhatti admin status ``` ## Description Reads the latest metrics snapshot and event log from the local SQLite database and prints a compact dashboard: * **Version + update notice** — current server version, latest GitHub release if newer. * **Host stats** — load average, memory in use vs. total. * **Sandbox totals** — broken down by thermal state (hot/warm/cold). * **User totals** — total users, currently-active users. * **API + proxy counters** — total requests, errors, auth failures, cold-wake count. * **Per-user breakdown** — sandbox count vs. limit, current running/stopped split. * **Recent events** — last \~10 events from the audit log with a one-line summary each. Reads database state directly; doesn't touch the engine. Safe to run while the daemon is busy. ## Examples ```bash sudo bhatti admin status ``` ```text Bhatti v1.10.0 Host: 0.42 load, 12.4 / 32.0 GB memory Sandboxes 17 total (4 hot, 6 warm, 7 cold) Users 8 total, 5 active API 184.2K requests, 423 errors, 12 auth failures Proxy 47.1K requests, 89 errors, 156 cold wakes USER SANDBOXES STATUS alice 3/10 2 running, 1 stopped bob 0/5 - worker-bot 5/5 5 running RECENT EVENTS 14:22:18 thermal.pause alice scraper idle=180s 14:21:45 sandbox.created bob test-1 cpus=2 mem=1024 image=minimal 14:20:11 proxy.error - staging alias=staging status=502 14:18:33 thermal.wake alice api from=cold 47ms ... ``` ```bash # Machine-readable sudo bhatti admin status --json | jq .gauges.sandboxes_hot ``` ## Options This command takes only global flags. See [Global flags](/docs/reference/cli/#global-flags) for `--url`, `--token`, `--json`, `--timing`, `--data-dir`. ## See also * [`bhatti admin events`](/docs/reference/cli/admin/admin-events/) — drill into the event log * [`bhatti admin metrics`](/docs/reference/cli/admin/admin-metrics/) — time-series view # bhatti completion > Generate shell completion scripts for bash, zsh, or fish. Generate a shell completion script. ## Synopsis ``` bhatti completion ``` ## Description Prints the completion script for the requested shell to stdout. Save it to the right place for your shell, restart, and tab-completion is enabled. **Sandbox-name completion** uses a local cache (`/tmp/bhatti-completions-`), populated by `create`, `destroy`, and `list`. It never hits the network, so completions are instant and work offline. The cache may be slightly stale right after another machine creates a sandbox; running `bhatti list` refreshes it. ## Examples ### bash ```bash # System-wide bhatti completion bash | sudo tee /etc/bash_completion.d/bhatti > /dev/null # Or per-user (no sudo) echo 'source <(bhatti completion bash)' >> ~/.bashrc ``` ### zsh ```bash bhatti completion zsh > "${fpath[1]}/_bhatti" # Or simpler echo 'source <(bhatti completion zsh)' >> ~/.zshrc ``` ### fish ```bash bhatti completion fish > ~/.config/fish/completions/bhatti.fish ``` ## Options This command takes no flags besides the positional shell argument. ## See also * [`bhatti setup`](/docs/reference/cli/admin/setup/) — `setup` detects your shell and prints the right snippet automatically. # bhatti serve > Start the bhatti daemon on Linux (KVM) or macOS (HVF). # bhatti serve server only Start the bhatti daemon. ## Synopsis ``` sudo bhatti serve ``` ## Description Starts the HTTP API listener, the thermal manager, the public proxy (if domain mode or `public_proxy_listen` is configured), the port scanner, and the metrics + event recorders. Recovers any previously running sandboxes from the SQLite database on startup, adopting their live `bhatti-vmm` helpers by PID. Requires: * Linux with KVM (`/dev/kvm`), or macOS on Apple Silicon (HVF) * Root on Linux (for `/dev/kvm`); macOS needs no root * Config at `/etc/bhatti/config.yaml` (or `$BHATTI_CONFIG`) * The runtime bundle (`bhatti-vmm`, `bhatti-netd`, `libkrun`, the lean kernel) and at least one rootfs image present at the paths in the config Logs are emitted as structured JSON to stdout. Set `BHATTI_LOG_LEVEL=debug` for verbose logging. Most operators run this under systemd; the install script sets up the unit file at `/etc/systemd/system/bhatti.service`. ## Examples ```bash # Foreground (development) sudo bhatti serve ``` ```bash # Under systemd (production) sudo systemctl start bhatti sudo journalctl -u bhatti -f ``` ```bash # Verbose logs BHATTI_LOG_LEVEL=debug sudo bhatti serve ``` ## Options This command takes no flags. Configuration is via `/etc/bhatti/config.yaml`. See [Configuration](/docs/reference/config/). ## Lifecycle * **Startup:** load config → open store → initialise engine → recover VMs → start servers (API + optional public proxy + optional internal loopback). * **Shutdown** (`SIGTERM` / `SIGINT`): drain HTTP, snapshot all running VMs to disk, close store, exit. Snapshots are restored on the next start. ## See also * [Configuration](/docs/reference/config/) * [Self-hosting](/docs/self-hosting/) # bhatti setup > Configure the CLI's API endpoint and key. Saves to ~/.bhatti/config.yaml. Interactive by default; --url and --token make it scriptable. Configure the CLI's API endpoint and key. ## Synopsis ``` bhatti setup [--url URL] [--token KEY] ``` ## Description Writes the API endpoint and key to `~/.bhatti/config.yaml` and tests the connection by listing sandboxes. The auth test hits `GET /sandboxes` (an authenticated endpoint), so a bad key fails immediately instead of appearing to succeed. With no flags, runs interactively — prompts for endpoint and key, masks the key as you type. With `--url` and `--token`, runs non-interactively. Use the latter form for agents, CI scripts, and provisioning tools. You usually don't need to run this on the server itself: the install script (`curl -fsSL bhatti.sh/install | sudo bash`) creates the admin user and writes the config to your `$SUDO_USER`'s `~/.bhatti/config.yaml` automatically. Run `setup` on a different machine that you want to drive the server from, or to switch your local CLI to a different bhatti server. ## Examples ### Interactive (the default) ```bash bhatti setup ``` ```text API endpoint [http://localhost:8080]: https://api.bhatti.sh API key: Saved to /home/alice/.bhatti/config.yaml Testing connection... ✓ authenticated (3 sandboxes) Enable completions: echo 'source <(bhatti completion zsh)' >> ~/.zshrc ``` ### Non-interactive (agents, CI, provisioners) ```bash bhatti setup --url https://api.bhatti.sh --token bht_abc123 ``` ```text Saved to /home/alice/.bhatti/config.yaml Testing connection... ✓ authenticated (3 sandboxes) ``` The completions hint is suppressed — there's no human reading the output anyway. If the connection test fails, the command exits non-zero so your provisioner can detect the failure. ## Options ``string required API endpoint URL. Sets the prompt's default. With `--token` set too, the prompt is skipped entirely and `setup` writes immediately. ``string required API key. With `--url` set too, the prompt is skipped and `setup` writes immediately. The auth test always runs. If you need to write the config without contacting a server (pre-baking an image, for example), edit `~/.bhatti/config.yaml` directly — it's just two YAML keys. See [Global flags](/docs/reference/cli/#global-flags) for `--url`, `--token` (the global versions of these — they override config but don't write to it; `setup`'s flags are the persisting form), `--json`, `--timing`, `--data-dir`. ## Configuration file After `setup`, your config looks like: ```yaml api_url: https://api.bhatti.sh auth_token: bht_abc123def456... ``` At runtime, this can be overridden with `--url`, `--token`, `BHATTI_URL`, or `BHATTI_TOKEN`. See [Configuration precedence](/docs/reference/cli/#configuration-precedence). ## See also * [`bhatti version`](/docs/reference/cli/admin/version/) — check connection without changing config * [`bhatti user create`](/docs/reference/cli/admin/user-create/) — create a user and get an API key (server-side, run by the operator) * [`bhatti completion`](/docs/reference/cli/admin/completion/) — shell completions * [Configuration](/docs/reference/config/) — full file format * [Self-hosting → adding teammates](/docs/self-hosting/#adding-teammates) — the full flow for setting someone up against a remote bhatti # bhatti update > Update bhatti. Auto-detects CLI vs server. Update bhatti to the latest release. ## Synopsis ``` bhatti update # auto-detect: CLI on a client, full update on a server sudo bhatti update # full server update (CLI, kernel, lohar, rootfs) sudo bhatti update --tiers all # also pull additional rootfs tiers bhatti update --cli-only # update just the CLI binary, even on a server ``` ## Description Downloads `https://bhatti.sh/install` and runs it. The installer auto-detects whether this is a server (config exists at `/etc/bhatti/config.yaml`) or a CLI-only host, and updates accordingly. **On a CLI-only machine:** updates the `bhatti` binary at `/usr/local/bin/bhatti`. Doesn't need root unless `/usr/local/bin` isn't writable by the user. **On a server:** updates everything — the CLI plus the runtime bundle (`bhatti-vmm`, `bhatti-netd`, `libkrun`, the lean kernel) and any installed rootfs tier images. Only changed components are re-downloaded; unchanged tiers and kernels are skipped. **`--cli-only`** forces the binary-only update path even on a server. Useful when you want to test a CLI fix without touching kernel/rootfs. **`--tiers`** takes a comma-separated list (or `all`) of additional rootfs tiers to install (`browser`, `docker`, `computer`). The default install only ships `minimal`; this is how you add more without re-running the full install command. Server updates require root (`/var/lib/bhatti` and the runtime bundle are root-owned). The command refuses up front if it detects a server install but the user isn't root, rather than partly-completing then failing inside the install script. ## Examples ```bash # CLI host bhatti update ``` ```bash # Server, default behaviour (full update) sudo bhatti update ``` ```bash # Server, also pull all tiers sudo bhatti update --tiers all ``` ```bash # Server, only update the CLI binary bhatti update --cli-only ``` ```bash # Server, add a single tier sudo bhatti update --tiers browser ``` ## Options \| Flag | Default | Description | | ---- | ------- | ----------- | | `--cli-only` | `false` | Update only the CLI binary, even on a server. | | `--tiers ` | — | Comma-separated rootfs tiers to install, or `all`. Server-only — has no effect on CLI hosts. | See [Global flags](/docs/reference/cli/#global-flags) for `--url`, `--token`, `--json`, `--timing`, `--data-dir`. ## See also * [`bhatti version`](/docs/reference/cli/admin/version/) — check current version + latest release * [Self-hosting](/docs/self-hosting/) — initial install * [Updating](/docs/updating/) — upgrade and uninstall guide # bhatti user create > Create a user with API key and per-user resource limits. # bhatti user create server only Create a user with an API key and per-user resource limits. ## Synopsis ``` sudo bhatti user create --name [--max-sandboxes ] [--max-cpus ] [--max-memory ] ``` ## Description Allocates a new user record in the local SQLite database. The install script already runs this once for `admin` during setup, so the first time you'd call `user create` yourself is when you want to add a teammate or a CI account. Each user gets: * A user ID (`usr_<8-hex>`). * An **API key** (`bht_<64-hex>`) — printed once, then only its SHA-256 hash is stored. **Save the key immediately**; it can't be recovered. Use [`user rotate-key`](/docs/reference/cli/admin/user-rotate-key/) if it's lost. * A **subnet index** — an integer assigned monotonically. It's a vestigial field carried over from v1; in v2, user isolation comes from API scoping and a per-owner `bhatti-netd` gateway, not a per-user bridge network. You don't need to think about it. * Per-sandbox resource caps (`--max-cpus`, `--max-memory`) and a per-user sandbox count cap (`--max-sandboxes`). Records the `user.created` event in the audit log. This is a **server-only** command — it operates directly on `/var/lib/bhatti/state.db`. Runs without the daemon needing to be up, but typically as `sudo` because the data dir is root-owned. ## Examples ```bash sudo bhatti user create --name alice ``` ```text User created: ID: usr_a1b2c3d4 Name: alice Subnet: 2 API key: bht_abc123def456... This key will not be shown again. Save it now. For remote CLI use: bhatti setup --url https://your-server:8080 --token bht_abc123def456... ``` ```bash # Custom limits sudo bhatti user create --name beefy \ --max-sandboxes 20 \ --max-cpus 8 \ --max-memory 16384 ``` ## Options \| Flag | Default | Description | | ---- | ------- | ----------- | | `--name ` | (required) | Username. Must be unique. | | `--max-sandboxes ` | `5` | Maximum concurrent sandboxes. | | `--max-cpus ` | `4` | Maximum vCPUs per sandbox (capped at create time). | | `--max-memory ` | `4096` | Maximum memory MB per sandbox (capped at create time). | See [Global flags](/docs/reference/cli/#global-flags) for `--url`, `--token`, `--json`, `--timing`, `--data-dir`. `--data-dir` is honoured here when you need to point at a non-default data directory. ## See also * [`bhatti user list`](/docs/reference/cli/admin/user-list/) * [`bhatti user rotate-key`](/docs/reference/cli/admin/user-rotate-key/) — replace a lost or compromised key * [Users & auth](/docs/managing/users/) — broader narrative on multi-tenant setup # bhatti user delete > Delete a user. Fails if the user has active sandboxes. # bhatti user delete server only Delete a user. ## Synopsis ``` sudo bhatti user delete [-y] ``` ## Description Removes the user record. The user's API key stops working immediately. **Fails if the user has active (non-destroyed) sandboxes.** Destroy them first: ```bash sudo bhatti user list # find them via the user's sandbox count # (server-side — to list a specific user's sandboxes you need the SQL or the user's API key) ``` Volumes, secrets, snapshots, and images owned by the user remain on disk after the user is deleted (orphaned). A future `--purge` mode could clean these up; for now, manual cleanup is required if you want to reclaim the space. By default, prompts for confirmation. In a non-interactive shell without `-y`, the command refuses and exits `1`. Records a `user.deleted` event in the audit log. ## Examples ```bash sudo bhatti user delete alice ``` ```text Delete user "alice"? [y/N]: y deleted user "alice" ``` ```bash # Active sandboxes still around sudo bhatti user delete alice ``` ```text Error: user "alice" has 3 active sandbox(es) ``` ## Options \| Flag | Default | Description | | ---- | ------- | ----------- | | `-y`, `--yes` | `false` | Skip confirmation. | See [Global flags](/docs/reference/cli/#global-flags) for `--url`, `--token`, `--json`, `--timing`, `--data-dir`. ## See also * [`bhatti user list`](/docs/reference/cli/admin/user-list/) * [`bhatti user rotate-key`](/docs/reference/cli/admin/user-rotate-key/) — usually a better answer than delete # bhatti user list > List users with their resource limits and current sandbox counts. # bhatti user list server only List all users on the server. ## Synopsis ``` sudo bhatti user list ``` ## Description Lists all users with ID, name, current sandbox count vs. limit, per-sandbox CPU and memory caps, and the subnet index. Reads from the local SQLite database. ## Examples ```bash sudo bhatti user list ``` ```text ID NAME SANDBOXES CPUS MEM SUBNET usr_a1b2c3d4 alice 3/10 4 4096 2 usr_e5f6g7h8 bob 0/5 4 4096 3 usr_i9j0k1l2 worker-bot 5/5 2 2048 4 ``` The `SANDBOXES` column is `current/limit` — `5/5` means at-cap. The `SUBNET` column is a vestigial per-user index carried over from v1 — in v2 it no longer maps to a bridge network (isolation is API scoping + the per-owner `bhatti-netd` gateway). ## Options This command takes only global flags. See [Global flags](/docs/reference/cli/#global-flags) for `--url`, `--token`, `--json`, `--timing`, `--data-dir`. ## See also * [`bhatti user create`](/docs/reference/cli/admin/user-create/) * [`bhatti admin status`](/docs/reference/cli/admin/admin-status/) — also shows per-user sandbox breakdown # bhatti user rotate-key > Rotate a user's API key. Old key is immediately invalidated. # bhatti user rotate-key server only Rotate a user's API key. ## Synopsis ``` sudo bhatti user rotate-key ``` ## Description Generates a new API key, stores its SHA-256 hash, and prints the plaintext **once**. The previous key is invalidated server-side immediately — any in-flight requests using the old key fail with `401`. Use this when: * A key may have leaked. * A user has lost their key (the original is unrecoverable). * You have a key-rotation policy. The user's existing sandboxes, secrets, volumes, snapshots, and images are all preserved — only the auth token changes. Records a `user.key_rotated` event in the audit log. ## Examples ```bash sudo bhatti user rotate-key alice ``` ```text API key rotated for "alice" New key: bht_xyz789... The old key is immediately invalidated. This key will not be shown again. Save it now. ``` After rotation, give the user the new key. They run `bhatti setup` to update their config. ## Options This command takes only global flags. See [Global flags](/docs/reference/cli/#global-flags) for `--url`, `--token`, `--json`, `--timing`, `--data-dir`. ## See also * [`bhatti user create`](/docs/reference/cli/admin/user-create/) * [`bhatti setup`](/docs/reference/cli/admin/setup/) — what the user runs to install the new key # bhatti version > Print CLI version, server version, and check for updates. Print CLI and server versions; check for updates. ## Synopsis ``` bhatti version ``` ## Description Prints: * **CLI version** — embedded at build time via `-ldflags`. Shows `dev` for unreleased builds. * **API endpoint** — currently configured from flags, env, or config. * **Server version** — read from the `X-Bhatti-Version` response header on a probe to `GET /sandboxes`. Skipped if unreachable or unauthenticated. * **Update notice** — if the CLI's GitHub release check (cached for 1 hour at `~/.bhatti/.latest-version`) finds a newer release, prints it. The server-version probe also drives the `X-Bhatti-Min-CLI` warning that appears on every command — if your CLI is older than the server's minimum, every command emits a one-line stderr warning. ## Examples ```bash bhatti version ``` ```text bhatti v1.10.0 api: https://api.bhatti.sh server: v1.10.0 ``` ```bash # When an update is available bhatti version ``` ```text bhatti v1.9.0 api: https://api.bhatti.sh server: v1.10.0 Update available: v1.9.0 → v1.10.0 (bhatti update) ``` ```bash # Machine-readable bhatti version --json ``` ```json { "version": "v1.10.0", "api": "https://api.bhatti.sh", "server_version": "v1.10.0", "latest_version": "v1.10.0" } ``` ## Options This command takes only global flags. See [Global flags](/docs/reference/cli/#global-flags) for `--url`, `--token`, `--json`, `--timing`, `--data-dir`. ## See also * [`bhatti update`](/docs/reference/cli/admin/update/) — apply available updates * [`bhatti admin status`](/docs/reference/cli/admin/admin-status/) — also prints version info on the server side # Execution & shells > Run commands, open shells, list active sessions, and discover listening ports inside sandboxes. Commands that drive execution inside a running sandbox: one-shot commands, interactive shells, and visibility into what’s currently running and listening. | Command | Description | | ------------------------------------------------- | ------------------------------------------------------------------------------- | | [`bhatti exec`](/docs/reference/cli/exec/exec/) | Run a command. Streaming or buffered output, optional timeout, optional detach. | | [`bhatti shell`](/docs/reference/cli/exec/shell/) | Open an interactive shell with detach/reattach semantics (alias: `sh`). | | [`bhatti ps`](/docs/reference/cli/exec/ps/) | List active sessions in a sandbox (init scripts, shells, detached execs). | | [`bhatti ports`](/docs/reference/cli/exec/ports/) | List listening ports detected inside a sandbox. | All commands wake cold sandboxes transparently — you don’t need to start the sandbox first. ## Quick patterns [Section titled “Quick patterns”](#quick-patterns) ```bash bhatti exec dev -- npm install # one-shot command bhatti exec dev -- bash -c 'echo $API_KEY' # shell expansion via /bin/bash bhatti exec dev --timeout 60 -- long-running.sh # cap runtime at 60s bhatti exec dev --detach -- ./worker.sh # run in background, return PID bhatti shell dev # interactive (Ctrl+\ to detach) bhatti shell dev --new # don't reattach to a running session bhatti ps dev # what's running inside? bhatti ports dev # what's listening? ``` ## See also [Section titled “See also”](#see-also) * [Sandbox commands](/docs/reference/cli/sandbox/) — create/list/destroy * [Files](/docs/reference/cli/files/) — read and write files inside sandboxes * [API: `POST /sandboxes/:id/exec`](/docs/reference/api/#run-a-command) — the underlying HTTP endpoint, streaming and detach modes # bhatti exec > Execute a command inside a sandbox. Streaming or buffered output, optional timeout, optional detach. Execute a command inside a sandbox. The exit code of the child process is forwarded to the CLI's exit code. Sleeping sandboxes wake automatically. ## Synopsis ``` bhatti exec [--] [flags] ``` ## Description The `--` separator is optional — `bhatti exec dev echo hello` works just like `bhatti exec dev -- echo hello`. Use `--` only when the command contains flags that cobra would otherwise try to parse (e.g. `bhatti exec dev -- ls -la /tmp`). Commands run as the unprivileged user `lohar` (uid 1000), not root. Use `sudo` inside the sandbox for root operations. Stdout and stderr are kept on separate streams; when output is captured, the result distinguishes them. **Output mode auto-detects:** * **Stdout is a TTY** → streaming NDJSON, line-by-line, flushed as the process emits it. You see `npm install`'s progress bar in real time. * **Stdout is a pipe or `--json` is set** → buffered. The CLI prints a JSON object `{exit_code, stdout, stderr}` once the command finishes. * **`BHATTI_FORCE_STREAM=1`** → forces streaming even when piping. Useful for CI logs that want progressive output. **Cold sandboxes wake transparently.** If the sandbox is `cold` or `warm`, the server resumes it before executing — typically \~50ms for cold, sub-millisecond for warm. The wake is invisible to the caller; the command just takes a bit longer. **Failures get recovery hints.** Common errors (sandbox not found, sandbox not running, name conflict, quota reached) are followed by a one-line suggestion in stderr. For example: ```text 404 Not Found: sandbox not found Check sandbox name: bhatti ls ``` ## Examples ```bash # Most basic bhatti exec dev -- echo hello ``` ```bash # -- is optional bhatti exec dev echo hello ``` ```bash # Pipe output to other tools — CLI switches to buffered mode automatically bhatti exec dev -- cat /workspace/data.json | jq .name ``` ```bash # Shell expansion needs a shell — env vars in the *guest*, not in your terminal bhatti exec dev -- bash -c 'echo $API_KEY' ``` ```bash # Cap a long-running command at 60s bhatti exec dev --timeout 60 -- ./benchmark.sh ``` ```bash # Run in the background, return immediately with the guest PID bhatti exec dev --detach -- ./worker.sh ``` ```text pid: 4218 output: /var/log/lohar/sessions/sess-7b3c.log ``` ## Options \| Flag | Default | Description | | ---- | ------- | ----------- | | `--timeout ` | `300` | Exec timeout in seconds. The server kills the process group on timeout and returns exit code `137`. Hard max: `86400` (24 hours). | | `--detach` | `false` | Run the command in the background and return immediately with the guest PID and the path to the captured output file. The session continues until it finishes or is killed. | See [Global flags](/docs/reference/cli/#global-flags) for `--url`, `--token`, `--json`, `--timing`, `--data-dir`. ## Exit codes \| Code | Meaning | | ---- | ------- | | `` | The child process's exit code is forwarded verbatim. | | `137` | The child was killed (timeout exceeded, or session manually killed). | | `1` | CLI / API error before the command ran (sandbox not found, network, auth). | ## See also * [`bhatti shell`](/docs/reference/cli/exec/shell/) — interactive shell with reattach * [`bhatti ps`](/docs/reference/cli/exec/ps/) — see detached / running sessions * [API: `POST /sandboxes/:id/exec`](/docs/reference/api/#run-a-command) # bhatti ports > List listening TCP ports detected inside a sandbox, with proxy URLs for each. List the listening TCP ports detected inside a sandbox. ## Synopsis ``` bhatti ports ``` ## Description The host periodically scans each running sandbox for listening ports and caches them. `bhatti ports` reads that cache and prints one row per port. Each row also shows the path-based proxy URL — a developer convenience for hitting the port from the host without a domain or `bhatti publish`. The proxy URL goes through the API server at `/sandboxes//proxy//...`. It requires the same Bearer auth as any other API call — useful for poking at services during development. For public, auth-free URLs, use [`bhatti publish`](/docs/reference/cli/networking/publish/). Cold sandboxes are not scanned (they have no listening processes by definition). To list ports for a sleeping sandbox, wake it first or just send a request that triggers a wake. ## Examples ```bash bhatti ports dev ``` ```text PORT URL 3000 http://localhost:8080/sandboxes/dev/proxy/3000/ 6379 http://localhost:8080/sandboxes/dev/proxy/6379/ ``` ```bash # Quick test against a service inside the sandbox bhatti ports dev --json | jq -r '.[] | .proxy_url' | head -1 | xargs curl -H "Authorization: Bearer $BHATTI_TOKEN" ``` ## Options This command takes only global flags. See [Global flags](/docs/reference/cli/#global-flags) for `--url`, `--token`, `--json`, `--timing`, `--data-dir`. ## See also * [`bhatti publish`](/docs/reference/cli/networking/publish/) — public auth-free URL for a port * [API: `GET /sandboxes/:id/ports`](/docs/reference/api/#list-listening-ports) * [API: `ANY /sandboxes/:id/proxy/:port/*`](/docs/reference/api/#reverse-proxy) # bhatti ps > List active sessions inside a sandbox — init scripts, shells, detached execs. List active sessions inside a sandbox. ## Synopsis ``` bhatti ps ``` ## Description A *session* is a process the guest agent is tracking — typically because it has a PTY, was started detached, or was launched as the sandbox's `--init` script. Active sessions persist across client disconnections and show up here regardless of whether anyone is attached. The `init` script (from `bhatti create --init "..."`) appears as a session named `init`. You can attach to it with [`bhatti shell`](/docs/reference/cli/exec/shell/) (passing its session ID via the API) to watch progress. Sessions without an attached client and without recent I/O are eventually garbage-collected by an idle timer. Active sessions (producing output or accepting input) are never killed by the timer. ## Examples ```bash bhatti ps dev ``` ```text ID COMMAND RUNNING ATTACHED init npm install && npm run dev true false s1 /bin/zsh -li true true s2 ./long-running-worker.sh true false ``` ```bash bhatti ps dev --json | jq '.[] | select(.attached == false) | .session_id' ``` ## Options This command takes only global flags. See [Global flags](/docs/reference/cli/#global-flags) for `--url`, `--token`, `--json`, `--timing`, `--data-dir`. ## See also * [`bhatti shell`](/docs/reference/cli/exec/shell/) — open or reattach to an interactive session * [`bhatti exec --detach`](/docs/reference/cli/exec/exec/) — start a session in the background * [API: `GET /sandboxes/:id/sessions`](/docs/reference/api/#list-sessions) # bhatti shell > Open an interactive shell inside a sandbox. Detach with Ctrl+\, reattach later, scrollback preserved. Open an interactive terminal inside a sandbox. Aliased as `bhatti sh`. ## Synopsis ``` bhatti shell [--new] bhatti sh ``` ## Description Opens a WebSocket connection to the sandbox's guest agent and attaches a PTY session. The default shell is `/bin/zsh`. Terminal resize is forwarded to the guest. **Detach with `Ctrl+\`.** The shell keeps running; output flows into a 64 KB scrollback ring buffer in the guest. Reconnect with `bhatti shell ` and you'll see the recent scrollback followed by live I/O. Detach does **not** send `SIGHUP` — child processes survive. **Single attached client per session.** If you reattach from a second machine, the first client is disconnected. Use `--new` to force a brand-new session instead of reattaching. **Clean exit vs. lost connection.** When the shell exits cleanly (Ctrl+D, `exit`), the CLI returns silently. Any other disconnection (network drop, server restart) shows: ```text connection lost (session s3f9 still running) reconnect: bhatti shell dev ``` The session keeps running in the guest. Reattach with the same command to pick it back up. ## Examples ```bash bhatti shell dev ``` ```bash # Force a new session, even if one is already attached bhatti shell dev --new ``` ```bash # Detach scenario: bhatti shell dev # attach, run things, hit Ctrl+\ # ...session keeps running in the guest... bhatti shell dev # reattach later, scrollback replayed ``` ## Options \| Flag | Default | Description | | ---- | ------- | ----------- | | `--new` | `false` | Don't reattach to an existing session. Start a fresh one. The previous session, if any, is left running. | See [Global flags](/docs/reference/cli/#global-flags) for `--url`, `--token`, `--json`, `--timing`, `--data-dir`. ## See also * [`bhatti exec`](/docs/reference/cli/exec/exec/) — non-interactive command execution * [`bhatti ps`](/docs/reference/cli/exec/ps/) — list active sessions including this shell * [`bhatti share`](/docs/reference/cli/networking/share/) — browser-based shell URL * [API: `GET /sandboxes/:id/ws`](/docs/reference/api/#interactive-shell-websocket) # File operations > Read, write, and list files inside sandboxes. Move bytes in and out of a sandbox without opening a shell. Useful for scripted setup, fetching results, and inspecting state. | Command | Description | | ------------------------------------------------------- | ----------------------------------------------- | | [`bhatti file read`](/docs/reference/cli/files/read/) | Read a file from a sandbox to stdout. | | [`bhatti file write`](/docs/reference/cli/files/write/) | Write a file from stdin into a sandbox. Atomic. | | [`bhatti file ls`](/docs/reference/cli/files/ls/) | List directory contents inside a sandbox. | The CLI exposes the simplest forms — read whole files, write whole files. The underlying API supports partial reads (`offset`, `limit`, `max_bytes`), permission control (`mode`), and HEAD-style stat requests; reach for it directly when you need those. See the [API reference](/docs/reference/api/#files) for the full surface. ## Quick patterns [Section titled “Quick patterns”](#quick-patterns) ```bash bhatti file read dev /workspace/app.js echo 'config' | bhatti file write dev /etc/config bhatti file ls dev /workspace/ bhatti file read dev /workspace/data.json | jq . ``` ## Limits [Section titled “Limits”](#limits) * Writes are capped at **100 MB** per request. * Directory listings are capped at **10 000 entries**; a sentinel entry indicates truncation. * Writes are atomic — temp file, fsync, rename. Concurrent readers never see a partial file. # bhatti file ls > List directory contents inside a sandbox. List directory contents inside a sandbox. ## Synopsis ``` bhatti file ls ``` ## Description Lists files and subdirectories at the given absolute path. The default human format prints one row per entry: a directory flag, mode, size in bytes, and name. Listings are capped at **10 000 entries**. If the directory has more entries than that, a sentinel row indicates truncation. For listings with this many entries you almost certainly want to be running `find` inside the sandbox via [`bhatti exec`](/docs/reference/cli/exec/exec/) and processing the results there instead. ## Examples ```bash bhatti file ls dev /workspace ``` ```text drwxr-xr-x 4096 node_modules -rw-r--r-- 1234 app.js -rw-r--r-- 256 package.json -rw-r--r-- 512 README.md ``` ```bash # JSON for scripting bhatti file ls dev /workspace --json | jq '.[] | select(.is_dir == false) | .name' ``` ## Output fields (JSON) ```json [ {"name": "app.js", "size": 1234, "mode": "0644", "is_dir": false, "mtime": 1761792345}, {"name": "node_modules", "size": 4096, "mode": "0755", "is_dir": true, "mtime": 1761792500} ] ``` `mtime` is a Unix timestamp in seconds. ## Options This command takes only global flags. See [Global flags](/docs/reference/cli/#global-flags) for `--url`, `--token`, `--json`, `--timing`, `--data-dir`. ## See also * [`bhatti file read`](/docs/reference/cli/files/read/) / [`bhatti file write`](/docs/reference/cli/files/write/) * [API: `GET /sandboxes/:id/files?ls=true`](/docs/reference/api/#list-directory) # bhatti file read > Read a file from a sandbox to stdout. Read a file from a sandbox and write its bytes to stdout. ## Synopsis ``` bhatti file read ``` ## Description Streams the file's contents to stdout, byte-for-byte. The path must be absolute. Cold sandboxes wake transparently before the read. The CLI always reads the whole file. The API supports partial reads (`offset`, `limit`, `max_bytes` query params) for tailing logs without transferring the entire file — call it directly when you need that. Every response includes an `X-File-Size` header so clients can detect truncation. See [API: files](/docs/reference/api/#files). ## Examples ```bash bhatti file read dev /workspace/app.js ``` ```bash # Pipe to other tools bhatti file read dev /workspace/data.json | jq .name ``` ```bash # Save to a local file bhatti file read dev /var/log/agent.log > agent-$(date +%F).log ``` ## Options This command takes only global flags. See [Global flags](/docs/reference/cli/#global-flags) for `--url`, `--token`, `--json`, `--timing`, `--data-dir`. ## See also * [`bhatti file write`](/docs/reference/cli/files/write/) — opposite direction * [`bhatti file ls`](/docs/reference/cli/files/ls/) — list a directory * [API: `GET /sandboxes/:id/files`](/docs/reference/api/#read-a-file) # bhatti file write > Write a file into a sandbox from stdin. Atomic, capped at 100 MB. Write a file into a sandbox. Reads bytes from stdin and writes them to the absolute `` inside the sandbox. ## Synopsis ``` bhatti file write ``` ## Description Writes are atomic — the guest agent writes to a temp file, fsyncs it, then renames it over the target. Concurrent readers never see partial content. Writes are capped at **100 MB** per request. For larger files, split into chunks or attach a [persistent volume](/docs/reference/cli/volumes/) instead. The CLI doesn't expose `--mode` for setting the file permissions (defaults to `0644`). The underlying [API endpoint](/docs/reference/api/#write-a-file) accepts a `mode=0755` query parameter — use it directly when you need to set a specific mode (e.g. `0700` for keys, `0755` for executables). ## Examples ```bash echo 'console.log("hello")' | bhatti file write dev /workspace/app.js ``` ```bash # From a local file bhatti file write dev /workspace/config.json < ./config.json ``` ```bash # Generate and write inline date | bhatti file write dev /tmp/last-deploy.txt ``` ```bash # Set executable bit via the API directly local_token="$BHATTI_TOKEN" curl -X PUT "http://localhost:8080/sandboxes/dev/files?path=/usr/local/bin/run.sh&mode=0755" \ -H "Authorization: Bearer $local_token" \ --data-binary @./run.sh ``` ## Options This command takes only global flags. See [Global flags](/docs/reference/cli/#global-flags) for `--url`, `--token`, `--json`, `--timing`, `--data-dir`. ## See also * [`bhatti file read`](/docs/reference/cli/files/read/) — opposite direction * [`bhatti create --file local:guest`](/docs/reference/cli/sandbox/create/) — inject a file at boot, no separate write call * [API: `PUT /sandboxes/:id/files`](/docs/reference/api/#write-a-file) # Images > Manage rootfs images. Pull from public OCI registries, import from Docker, save sandboxes as reusable images. Images are ext4 filesystem snapshots used as sandbox root filesystems. bhatti ships pre-built tier images (`minimal`, `browser`, `docker`, `computer`); you can also pull from public OCI registries, import from Docker, or save a running sandbox’s filesystem as an image. | Command | Description | | ------------------------------------------------------------- | ---------------------------------------------------------- | | [`bhatti image list`](/docs/reference/cli/images/list/) | List available images. | | [`bhatti image pull`](/docs/reference/cli/images/pull/) | Pull from a public OCI registry. Async; returns a task ID. | | [`bhatti image import`](/docs/reference/cli/images/import/) | Import a local Docker image, or a tarball with `--tar`. | | [`bhatti image save`](/docs/reference/cli/images/save/) | Save a running sandbox’s rootfs as a reusable image. | | [`bhatti image delete`](/docs/reference/cli/images/delete/) | Delete an image. | | [`bhatti image share`](/docs/reference/cli/images/share/) | Share an image with specific users (server-only). | | [`bhatti image unshare`](/docs/reference/cli/images/unshare/) | Revoke image access from users (server-only). | ## Built-in tiers [Section titled “Built-in tiers”](#built-in-tiers) | Tier | What’s in it | Size | | ---------- | --------------------------------- | -------- | | `minimal` | Bare Ubuntu 24.04 + curl + fuse3 | \~200 MB | | `browser` | + Chromium, Playwright, Node 22 | \~600 MB | | `docker` | + Docker Engine | \~550 MB | | `computer` | + XFCE desktop, KasmVNC, Chromium | \~1.5 GB | ```bash bhatti create --name scraper --image browser bhatti create --name ci --image docker ``` The server auto-discovers tiers from `/var/lib/bhatti/images/`. Install more with `sudo bhatti update --tiers all`. ## Image scoping [Section titled “Image scoping”](#image-scoping) Each image is owned by the user who created it (or by the system, for built-in tiers). Other users can’t see or use your custom images by default. To share with a specific user: [`sudo bhatti image share --user alice`](/docs/reference/cli/images/share/). The command operates directly on the local SQLite database, so it requires running on the server with DB access — it isn’t an API call you can make remotely. Use `--list` to inspect current shares; pass `--user alice --user bob` to share with multiple users in one call. [`unshare`](/docs/reference/cli/images/unshare/) revokes access by user. # bhatti image delete > Delete an image. Cannot be undone. Delete an image. ## Synopsis ``` bhatti image delete [-y] ``` ## Description Removes the image record and the underlying ext4 file. **Cannot be undone.** Sandboxes that were created from this image keep running — they have their own copy-on-write rootfs derived from the image at creation time. They don't reference the original after boot. By default, `delete` prompts for confirmation. In a non-interactive shell without `-y`, the command refuses and exits `1`. You cannot delete a system tier image (`minimal`, `browser`, etc.) — those are owned by `user_id=""` and only the operator can remove them by removing the file from `/var/lib/bhatti/images/`. ## Examples ```bash bhatti image delete python-3.12 ``` ```text Delete image "python-3.12"? [y/N]: y deleted ``` ```bash # Non-interactive bhatti image delete python-3.12 -y ``` ## Options \| Flag | Default | Description | | ---- | ------- | ----------- | | `-y`, `--yes` | `false` | Skip confirmation. | See [Global flags](/docs/reference/cli/#global-flags) for `--url`, `--token`, `--json`, `--timing`, `--data-dir`. ## See also * [`bhatti image list`](/docs/reference/cli/images/list/) * [API: `DELETE /images/:name`](/docs/reference/api/#delete-an-image) # bhatti image import > Import a local Docker image (via docker save) or a raw tarball. Import an image from a local Docker daemon, or directly from a tarball without Docker. ## Synopsis ``` bhatti image import [--name ] bhatti image import --tar --name ``` ## Description Two modes: **Docker mode** (default). The CLI runs `docker save ` on your machine and streams the resulting tarball to the bhatti server, which converts it to an ext4 rootfs. Requires Docker installed locally. The image must already be in your local Docker daemon — pull it first with `docker pull` if needed. **Tarball mode** (`--tar`). Skip Docker entirely. The CLI reads the file at `` and streams it to the server. The tarball must be in the format produced by `docker save` (an OCI layout or v1 image manifest). `--name` is required in this mode since there's no ref to derive the name from. If you don't pass `--name` in Docker mode, a name is derived from the ref the same way as `image pull`: `python:3.12` → `python-3.12`. For private registries, this is the recommended path — `docker pull` handles authentication on your machine using your existing Docker credentials, then bhatti just receives the bytes: ```bash docker pull ghcr.io/org/private:latest bhatti image import ghcr.io/org/private:latest ``` ## Examples ```bash # Import from local Docker (name derived from ref) bhatti image import python:3.12 ``` ```bash # Private image: pull with Docker first, then import docker pull ghcr.io/org/private:latest bhatti image import ghcr.io/org/private:latest ``` ```bash # Locally-built image docker build -t my-env . bhatti image import my-env ``` ```bash # Custom name override bhatti image import python:3.12 --name py312 ``` ```bash # Raw tarball — no Docker needed docker save ubuntu:24.04 -o /tmp/ubuntu.tar bhatti image import --tar /tmp/ubuntu.tar --name ubuntu-24 ``` ## Failure modes * **Docker not found**: install Docker, or use `--tar` with a tarball. * **Image not in local Docker**: `docker pull ` first, or use `--tar` with a saved tarball. * **Server rejects the upload**: image is too large or malformed; check stderr for the server message. ## Options \| Flag | Default | Description | | ---- | ------- | ----------- | | `--name ` | derived from ref (Docker mode); required (`--tar` mode) | Image name to register on the server. | | `--tar ` | — | Skip Docker; stream the given tarball directly. | See [Global flags](/docs/reference/cli/#global-flags) for `--url`, `--token`, `--json`, `--timing`, `--data-dir`. ## See also * [`bhatti image pull`](/docs/reference/cli/images/pull/) — server-side pull from public registries (no Docker needed) * [`bhatti image save`](/docs/reference/cli/images/save/) — save a sandbox's filesystem as an image * [API: `POST /images/import`](/docs/reference/api/#import-an-image) # bhatti image list > List all rootfs images available to the authenticated user. List all rootfs images available to the authenticated user. ## Synopsis ``` bhatti image list ``` ## Description Lists images in three scopes: * **System tiers** (`minimal`, `browser`, `docker`, `computer`) — visible to everyone. * **Your own images** — created via `image pull`, `image import`, or `image save`. * **Images shared with you** by another user. ## Examples ```bash bhatti image list ``` ```text ID NAME SOURCE SIZE SCOPE img_abc123 minimal built-in 186MB admin img_def456 browser built-in 612MB admin img_ghi789 python-3.12 oci:python:3.12 284MB user img_jkl012 my-stack snapshot:dev 512MB user ``` ```bash # JSON for scripting bhatti image list --json | jq '.[] | select(.user_id != "") | .name' ``` ## Output fields (JSON) ```json [ {"id": "img_abc123", "name": "minimal", "source": "built-in", "size_mb": 186, "user_id": ""}, {"id": "img_jkl012", "name": "my-stack", "source": "snapshot:dev", "size_mb": 512, "user_id": "usr_..."} ] ``` `user_id` is empty for system images; non-empty values denote ownership. ## Options This command takes only global flags. See [Global flags](/docs/reference/cli/#global-flags) for `--url`, `--token`, `--json`, `--timing`, `--data-dir`. ## See also * [`bhatti image pull`](/docs/reference/cli/images/pull/) / [`import`](/docs/reference/cli/images/import/) / [`save`](/docs/reference/cli/images/save/) * [API: `GET /images`](/docs/reference/api/#list-images) # bhatti image pull > Pull a public OCI/Docker image and convert it to a bhatti rootfs. Pull a public image from any OCI-compatible registry. The server fetches the image, flattens its layers, and writes them to an ext4 file usable as a sandbox rootfs. ## Synopsis ``` bhatti image pull [--name ] [--auth ] ``` ## Description The pull runs asynchronously on the server. The CLI returns a task ID, then polls `/tasks/` every 2 seconds and prints progress until the task completes or fails. **Press `Ctrl+C` to detach** — the pull continues on the server. Check progress later with `bhatti image list` (the image appears once it's ready). If you don't pass `--name`, a name is derived from the ref: * `python:3.12` → `python-3.12` * `node:22-slim` → `node-22-slim` * `ghcr.io/myorg/sandbox-base:latest` → `sandbox-base-latest` `--auth` enables a single-shot HTTP basic auth header (`user:token`). For real private registries with rotating credentials, prefer the [`image import`](/docs/reference/cli/images/import/) flow — `docker pull` handles auth on your machine, then bhatti just receives the tarball. If the same image was previously pulled with the same content digest, the server returns immediately without re-downloading. ## Examples ```bash bhatti image pull python:3.12 ``` ```text pulling python:3.12 as "python-3.12" (task: tsk_abc123) resolving manifest... downloading layers (45 MB / 142 MB)... flattening to ext4... done ``` ```bash # Custom name bhatti image pull node:22-slim --name node-22 # Detach: press Ctrl+C after seeing the task ID bhatti image pull ubuntu:24.04 ^C Interrupted. Pull continues on server. Check status: bhatti image list ``` ```bash # Use the pulled image bhatti create --name py --image python-3.12 ``` ## Authentication For private registries, `--auth` works for simple `user:token` schemes. If the pull fails with `unauthorized` / `403`, the CLI prints a hint suggesting you use `image import` instead: ```text This image may require authentication. Pull it with Docker locally, then import: docker pull ghcr.io/org/private:latest bhatti image import ghcr.io/org/private:latest ``` ## Options \| Flag | Default | Description | | ---- | ------- | ----------- | | `--name ` | derived from ref | Image name to register on the server. | | `--auth ` | — | HTTP basic auth credentials for the registry. | See [Global flags](/docs/reference/cli/#global-flags) for `--url`, `--token`, `--json`, `--timing`, `--data-dir`. ## See also * [`bhatti image import`](/docs/reference/cli/images/import/) — local Docker / tarball import (handles private registries via local auth) * [`bhatti create --image`](/docs/reference/cli/sandbox/create/) — use the pulled image * [API: `POST /images/pull`](/docs/reference/api/#pull-an-image) * [API: `GET /tasks/:id`](/docs/reference/api/#poll-an-async-task) # bhatti image save > Save a running sandbox's rootfs as a reusable image. Snapshot a running sandbox's filesystem and save it as a reusable image. ## Synopsis ``` bhatti image save --name ``` ## Description Captures the current state of the sandbox's rootfs (every file written since boot) and writes it to a new ext4 image owned by the user. The sandbox keeps running; the save is read-only from its perspective. Only the rootfs is captured — attached persistent volumes are **not** included. If your sandbox stores work in a volume, save the volume separately with [`bhatti volume clone`](/docs/reference/cli/volumes/clone/) and recreate the attachment when stamping out new sandboxes from the saved image. The saved image is private to the user. To make it available to other users, use [`bhatti image share`](/docs/reference/cli/images/share/) (server-only). ## Examples ```bash # Build a custom environment, then save it bhatti create --name build --image minimal bhatti exec build -- apt-get update && apt-get install -y nodejs bhatti exec build -- npm install -g pnpm bhatti image save build --name node-app # saved "node-app" (512MB) ``` ```bash # Stamp out workers from the saved image bhatti create --name worker-1 --image node-app bhatti create --name worker-2 --image node-app ``` ## Options \| Flag | Default | Description | | ---- | ------- | ----------- | | `--name ` | (required) | Name for the saved image. | See [Global flags](/docs/reference/cli/#global-flags) for `--url`, `--token`, `--json`, `--timing`, `--data-dir`. ## See also * [`bhatti volume clone`](/docs/reference/cli/volumes/clone/) — for snapshotting volume data alongside an image * [`bhatti snapshot create`](/docs/reference/cli/snapshots/create/) — full VM snapshot (memory + disk + processes), not just filesystem * [API: `POST /sandboxes/:id/save-image`](/docs/reference/api/#save-a-sandbox-as-an-image) # bhatti image share > Share an image with specific users. Operates directly on the local SQLite database; requires DB access (server-only). # bhatti image share server only Share an image with one or more specific users. ## Synopsis ``` bhatti image share --user [--user ]... bhatti image share --list ``` ## Description Adds an entry to the image-share table for each `` so that user can reference the image with `--image ` from their sandbox creation calls. Sharing is per-user; there's no "public" or "everyone" mode. The command operates directly on the server's SQLite database — it isn't an HTTP API call. You must run it on the server, with access to `/var/lib/bhatti/state.db` (typically as `sudo`). `--list` prints the users an image is currently shared with. `--unshare` is its own command, [`bhatti image unshare`](/docs/reference/cli/images/unshare/). ## Examples ```bash # Share with two users sudo bhatti image share spc-golden --user kowshik --user sumo ``` ```text shared "spc-golden" with: kowshik, sumo ``` ```bash # Inspect current shares sudo bhatti image share spc-golden --list ``` ```text spc-golden shared with: kowshik, sumo ``` ```bash # Image not yet shared sudo bhatti image share spc-golden --list ``` ```text spc-golden: not shared ``` ## Options \| Flag | Default | Description | | ---- | ------- | ----------- | | `--user ` | — | User to share with. Repeatable. Required unless `--list`. | | `--list` | `false` | Print current shares for the image instead of adding new ones. | See [Global flags](/docs/reference/cli/#global-flags) for `--url`, `--token`, `--json`, `--timing`, `--data-dir`. ## Failure modes * **Image not found**: bhatti can't see your image because of name typos or because it's owned by a different user. Check with `bhatti image list`. * **User not found**: the `` doesn't match any user. Check with `bhatti user list`. * **No `--user`**: the command refuses to do anything without an explicit user list (or `--list`). There's no global "share with all" mode. ## See also * [`bhatti image unshare`](/docs/reference/cli/images/unshare/) — revoke access * [`bhatti user list`](/docs/reference/cli/admin/user-list/) — find usernames # bhatti image unshare > Revoke image access from specific users. Server-only. # bhatti image unshare server only Revoke an image's share entries for one or more users. ## Synopsis ``` bhatti image unshare --user [--user ]... ``` ## Description Removes share-table entries for the listed users. After unsharing, those users will no longer see the image in [`bhatti image list`](/docs/reference/cli/images/list/) and can't reference it by name in `bhatti create --image`. Sandboxes the affected users already created from the image keep running — they have their own copy-on-write rootfs and don't reference the original. Like [`image share`](/docs/reference/cli/images/share/), this operates directly on the local SQLite database. Requires DB access on the server (typically `sudo`). ## Examples ```bash sudo bhatti image unshare spc-golden --user sumo ``` ```text unshared "spc-golden" from: sumo ``` ## Options \| Flag | Default | Description | | ---- | ------- | ----------- | | `--user ` | (required) | User to revoke. Repeatable. | See [Global flags](/docs/reference/cli/#global-flags) for `--url`, `--token`, `--json`, `--timing`, `--data-dir`. ## See also * [`bhatti image share`](/docs/reference/cli/images/share/) — add a share * [`bhatti image share --list`](/docs/reference/cli/images/share/) — see who has access # Networking & sharing > Publish ports with public URLs, share browser-based shells. Expose sandbox services to the world (or a teammate) without an API key on the receiver’s side. | Command | Description | | --------------------------------------------------------------- | --------------------------------------------------------------------------------- | | [`bhatti publish`](/docs/reference/cli/networking/publish/) | Publish a sandbox port at a public URL. Cold sandboxes wake on the first request. | | [`bhatti unpublish`](/docs/reference/cli/networking/unpublish/) | Remove a published port. | | [`bhatti share`](/docs/reference/cli/networking/share/) | Generate a one-shot URL that opens an interactive terminal in the browser. | Both `publish` and `share` produce URLs that don’t require an API key. Treat them like any other secret — anyone with the URL has access. ## Quick patterns [Section titled “Quick patterns”](#quick-patterns) ```bash # Publish on a friendly subdomain bhatti publish dev -p 3000 -a my-app # → https://my-app.bhatti.sh # Auto-generated alias (random suffix prevents URL guessing) bhatti publish dev -p 3000 # → https://dev-k3m9x2.bhatti.sh # Publish + give a teammate a browser shell in one call bhatti publish dev -p 3000 -a demo --shell # Share a sandbox shell over the web bhatti share dev # → https://api.bhatti.sh/_shell/#token=... # Revoke any active share link bhatti share dev --revoke # Tear down a published port bhatti unpublish dev -p 3000 ``` ## Domain mode vs. path-based [Section titled “Domain mode vs. path-based”](#domain-mode-vs-path-based) Public URLs at `https://.` only work when the server is configured with a [custom domain](/docs/managing/custom-domain/). Without one, `bhatti publish` falls back to a path-based proxy: ```text http:///sandboxes//proxy// ``` That URL still works, but it requires Bearer auth. Useful for development; not suitable for sharing publicly. See [`bhatti publish`](/docs/reference/cli/networking/publish/) for the exact fallback behaviour. # bhatti publish > Publish a sandbox port with a public URL. Cold sandboxes wake on first request. Publish a sandbox port at a public URL. ## Synopsis ``` bhatti publish -p [-a ] [--shell] ``` ## Description Creates a routing rule on the bhatti server: incoming requests to `https://.` get reverse-proxied to `localhost:` inside the sandbox. **Aliases.** With `-a my-app`, the URL is `https://my-app.` exactly. Without `-a`, an alias is generated from the sandbox name with a random suffix (`dev-k3m9x2`) to prevent URL guessing — useful for ephemeral previews. **Cold sandboxes wake automatically.** The first request after a sandbox went cold takes \~50ms longer than usual; subsequent requests are full speed. **No auth.** Published URLs are public. Anyone with the URL can reach the port. There's no per-URL access control — share carefully. **HTTP and WebSocket.** Both work through the proxy. WebSocket idle timeout is 10 minutes; HTTP body cap is 50 MB per request. **`--shell` combo.** Pass `--shell` to also generate a [web shell token](/docs/reference/cli/networking/share/) in the same call. Useful for "send a teammate a preview URL and a debug terminal in one click". **Without a custom domain configured.** If the server isn't running in [domain mode](/docs/managing/custom-domain/), there's no `.` URL. The CLI prints a fallback hint: ```text Publishing requires a custom domain. Setup: https://bhatti.sh/docs/managing/custom-domain In the meantime, use the proxy URL directly: http:///sandboxes//proxy// ``` The proxy URL works but requires Bearer auth, so it's developer-only. ## Examples ```bash # Friendly subdomain bhatti publish dev -p 3000 -a my-app # Published: https://my-app.bhatti.sh ``` ```bash # Auto-generated alias bhatti publish dev -p 3000 # Published: https://dev-k3m9x2.bhatti.sh ``` ```bash # Preview URL + browser shell, single call bhatti publish dev -p 3000 -a demo --shell # Published: https://demo.bhatti.sh # Shell: https://api.bhatti.sh/_shell/#token=... ``` ## Options \| Flag | Default | Description | | ---- | ------- | ----------- | | `-p`, `--port ` | (required) | Port inside the sandbox to publish. | | `-a`, `--alias ` | auto-generated | Custom subdomain alias. Without this, an alias derived from the sandbox name with a random suffix is used. | | `--shell` | `false` | Also generate a [web shell URL](/docs/reference/cli/networking/share/) in the same call. The shell URL is printed alongside the published URL. | See [Global flags](/docs/reference/cli/#global-flags) for `--url`, `--token`, `--json`, `--timing`, `--data-dir`. ## See also * [`bhatti unpublish`](/docs/reference/cli/networking/unpublish/) — remove a published port * [`bhatti share`](/docs/reference/cli/networking/share/) — web shell URL on its own * [Custom domain](/docs/managing/custom-domain/) — required for `.` URLs * [API: `POST /sandboxes/:id/publish`](/docs/reference/api/#publish-a-port) # bhatti share > Generate a one-shot URL that opens an interactive terminal in the browser. Generate a URL that opens an interactive terminal in the browser. No CLI install or API key needed on the receiver's side. ## Synopsis ``` bhatti share [--revoke] ``` ## Description Mints a fresh token bound to the sandbox and embeds it in the URL fragment (`#token=...`). The fragment is **not** sent to the server in HTTP request lines, so the token doesn't appear in access logs or referer headers — but anyone with the URL has shell access until the token is revoked. **Each call invalidates the previous token.** Calling `bhatti share dev` again generates a new URL; the old one stops working. Only one share link is active at a time per sandbox. **Scope.** A share token grants access *only* to the specific sandbox it was minted for. It is not interchangeable with the user's API key. **Behaviour in the browser.** The page opens an xterm.js terminal connected via WebSocket; it's a full PTY session, identical to [`bhatti shell`](/docs/reference/cli/exec/shell/). Closing the tab detaches; the session keeps running and scrollback is preserved. Reopening the URL reattaches. **Revoke** with `--revoke` — the token is deleted server-side and the URL stops working. ## Examples ```bash bhatti share dev ``` ```text Shell: https://api.bhatti.sh/_shell/a1b2c3d4#token=k3m9x2qr... ``` ```bash # JSON for scripting bhatti share dev --json ``` ```json { "url": "https://api.bhatti.sh/_shell/a1b2c3d4#token=k3m9x2qr...", "token": "k3m9x2qr..." } ``` ```bash # Revoke bhatti share dev --revoke ``` ```text Shell access revoked. ``` ## Options \| Flag | Default | Description | | ---- | ------- | ----------- | | `--revoke` | `false` | Delete the active token for this sandbox. The shared URL stops working immediately. | See [Global flags](/docs/reference/cli/#global-flags) for `--url`, `--token`, `--json`, `--timing`, `--data-dir`. ## See also * [`bhatti publish --shell`](/docs/reference/cli/networking/publish/) — preview URL + share URL in one call * [`bhatti shell`](/docs/reference/cli/exec/shell/) — terminal-based interactive shell * [`bhatti shell`](/docs/reference/cli/exec/shell/) — the CLI counterpart * [API: `POST /sandboxes/:id/shell-token`](/docs/reference/api/#mint-a-shell-token) # bhatti unpublish > Remove a published port. The URL stops working immediately. Remove a published port. The URL stops working immediately. ## Synopsis ``` bhatti unpublish -p ``` ## Description Deletes the routing rule. New requests to the previously published URL get a 404 from the proxy. In-flight WebSocket connections continue until the client closes them. Publish rules are also automatically cleaned up when a sandbox is destroyed — `unpublish` is only needed when you want to keep the sandbox but stop exposing the port. ## Examples ```bash bhatti unpublish dev -p 3000 ``` ```text Unpublished port 3000 ``` ## Options \| Flag | Default | Description | | ---- | ------- | ----------- | | `-p`, `--port ` | (required) | Port to unpublish. | See [Global flags](/docs/reference/cli/#global-flags) for `--url`, `--token`, `--json`, `--timing`, `--data-dir`. ## See also * [`bhatti publish`](/docs/reference/cli/networking/publish/) * [API: `DELETE /sandboxes/:id/publish/:port`](/docs/reference/api/#unpublish-a-port) # Sandbox commands > Lifecycle commands for sandbox VMs — create, list, inspect, edit, stop, start, destroy. Sandboxes are isolated Linux VMs. These commands cover the full lifecycle: create them, look at them, change mutable settings, pause and resume them on disk, and tear them down. | Command | Description | | -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ | | [`bhatti create`](/docs/reference/cli/sandbox/create/) | Create a new sandbox VM. Optional flags cover CPU, memory, env vars, init scripts, volumes, secrets, and file injection. | | [`bhatti list`](/docs/reference/cli/sandbox/list/) | List your sandboxes (alias: `ls`). `-o wide` adds resource columns. | | [`bhatti inspect`](/docs/reference/cli/sandbox/inspect/) | Show full details for one sandbox (alias: `info`) — kubectl-`describe` style. | | [`bhatti edit`](/docs/reference/cli/sandbox/edit/) | Toggle mutable settings. Currently only `--keep-hot` / `--allow-cold`. | | [`bhatti stop`](/docs/reference/cli/sandbox/stop/) | Snapshot to disk and free memory. | | [`bhatti start`](/docs/reference/cli/sandbox/start/) | Resume from snapshot. Continues exactly where it left off. | | [`bhatti destroy`](/docs/reference/cli/sandbox/destroy/) | Permanently destroy a sandbox (alias: `rm`). Volumes are detached, not deleted. | All commands accept either the sandbox name or its ID. Names are easier; IDs are stable across renames. ## Quick patterns [Section titled “Quick patterns”](#quick-patterns) ```bash bhatti create --name dev --cpus 2 --memory 1024 bhatti ls -o wide bhatti inspect dev bhatti exec dev -- npm install bhatti stop dev # save state, free memory bhatti start dev # resume from where it stopped bhatti destroy dev -y ``` In normal operation you don’t need `stop` and `start` — idle sandboxes are paused and resumed automatically. See [Thermal states](/docs/under-the-hood/thermal-states/) for the engineering. Use them to control thermal state explicitly when you know a sandbox will be idle for a while. ## See also [Section titled “See also”](#see-also) * [Execution & shells](/docs/reference/cli/exec/) — `exec`, `shell`, `ps`, `ports` * [Files](/docs/reference/cli/files/) — read/write files inside a sandbox * [Networking & sharing](/docs/reference/cli/networking/) — `publish`, `share` * [API: `POST /sandboxes`](/docs/reference/api/#create-a-sandbox) # bhatti create > Create a new sandbox VM with custom resources, environment, init script, volumes, and secrets. Create a new sandbox VM. Each sandbox is an isolated Linux environment with its own kernel, filesystem, and network interface — created in seconds, paused for free, resumed in microseconds. ## Synopsis ``` bhatti create [flags] ``` ## Description Boots a krucible microVM and registers it under the authenticated user. Returns once the guest agent is responding. The created sandbox is in the `running` (hot) thermal state. Without `--keep-hot`, it pauses to *warm* after 30 seconds of inactivity and freezes to *cold* (memory snapshotted to disk, RAM freed) after 30 minutes. Any incoming request transparently wakes it. `--volume` and `--secret` reference resources that must already exist (create them with [`bhatti volume create`](/docs/reference/cli/volumes/create/) and [`bhatti secret set`](/docs/reference/cli/secrets/set/)). `--file` reads a local file at submit time and writes its bytes to the guest path during boot — convenient for injecting config files without a volume. When used with `--template`, request-side `--secret` and `--file` are merged into the template's defaults: secrets from both lists are resolved (request-side names that don't exist in your secret store return `400`); `--file` adds to the template (templates have no files of their own). For environment variables, `--secret` wins over `--env` for the same name. `--cpus` accepts fractional values (e.g. `0.5`, `1.5`); the host scheduler runs vCPUs as time-shared threads, so fractional allocation maps to "you're entitled to \~N vCPU-seconds per second." `--hugepages` uses 2MB hugepages for the VM's memory, which makes boot faster. Use it for short-lived sandboxes, not for long-lived agents. The `--memory` server default is 1024 MB. The `--cpus` default is 1. Both are capped by the user's per-sandbox limits set via [`bhatti user create`](/docs/reference/cli/admin/user-create/). ## Examples ```bash # Smallest possible sandbox bhatti create --name dev ``` ```bash # Custom resources, env vars, and an init script bhatti create --name api \ --cpus 2 --memory 2048 \ --env NODE_ENV=production,LOG_LEVEL=info \ --init "cd /workspace && npm install" ``` ```bash # Mount a persistent volume; create the volume first bhatti volume create --name workspace --size 5120 bhatti create --name dev --volume workspace:/workspace ``` ```bash # Reference a stored secret as a guest env var bhatti secret set OPENAI_KEY sk-... bhatti create --name agent --secret OPENAI_KEY ``` ```bash # Inject a local config file at boot bhatti create --name worker --file ./config.json:/etc/worker/config.json ``` ```bash # Use a non-default rootfs image (browser tier with Chromium + Playwright) bhatti create --name scraper --image browser ``` ```bash # Autonomous agent — never paused, even when idle bhatti create --name agent --init "hermes gateway" --keep-hot ``` ## Options \| Flag | Default | Description | | ---- | ------- | ----------- | | `--name ` | auto-generated | Sandbox name. Must match `[a-zA-Z0-9][a-zA-Z0-9._-]{0,62}`. | | `--cpus ` | `1` | Number of vCPUs. Fractional values allowed (e.g. `0.5`). Capped by per-sandbox limit. | | `--memory ` | `0` (server uses `1024`) | Memory in MB. `0` means use the server default. Capped by per-sandbox limit. | | `--disk-size ` | `0` (use image size) | Rootfs disk size in MB. `0` means inherit from the base image. | | `--env ` | — | Environment variables, comma-separated, applied to every command. | | `--init ` | — | Init script that runs at boot as an attachable session named `init`. Survives the boot path; visible in [`bhatti ps`](/docs/reference/cli/exec/ps/). | | `--keep-hot` | `false` | Disable thermal transitions. Use for sandboxes that maintain external connections (websockets, polling agents). | | `--hugepages` | `false` | Use 2MB hugepages. Faster boot. | | `--template ` | — | Create from a template. Template fields are defaults; flags here override them. | | `--image ` | — | Rootfs image. Built-in: `minimal`, `browser`, `docker`, `computer`. Or a name from [`bhatti image list`](/docs/reference/cli/images/list/). | | `--volume ` | — | Attach a persistent volume. Format: `name:mount[:ro]`. Repeatable. | | `--secret ` | — | Reference a stored secret by name. Decrypted and exposed as an env var. Repeatable. | | `--file ` | — | Read a local file and write it to `guest` inside the sandbox at boot. Repeatable. | See [Global flags](/docs/reference/cli/#global-flags) for `--url`, `--token`, `--json`, `--timing`, `--data-dir`. ## Output Default output is a verbose summary: ``` sandbox/dev created (1 vCPU, 1024 MB) Shell: bhatti shell dev ``` With `--json`, the full sandbox object is printed. If a sandbox with the requested name already exists *and is identical*, the server returns the existing record and the CLI prints: ``` sandbox/dev unchanged (already exists) ``` This makes `create` safe to re-run from scripts. ## Exit codes \| Code | Meaning | | ---- | ------- | | `0` | Sandbox created, or already existed with the same configuration. | | `1` | Validation error, quota exceeded (e.g. `max-sandboxes` reached), name conflict, or boot failure. | ## See also * [`bhatti list`](/docs/reference/cli/sandbox/list/) — find your sandboxes * [`bhatti exec`](/docs/reference/cli/exec/exec/) — run a command in the new sandbox * [`bhatti shell`](/docs/reference/cli/exec/shell/) — open an interactive shell * [`bhatti destroy`](/docs/reference/cli/sandbox/destroy/) — clean up * [API: `POST /sandboxes`](/docs/reference/api/#create-a-sandbox) # bhatti destroy > Permanently destroy a sandbox and all its data. Permanently destroy a sandbox. Aliased as `bhatti rm`. ## Synopsis ``` bhatti destroy [-y] bhatti rm [-y] ``` ## Description Kills the `bhatti-vmm` process, removes the CoW rootfs overlay and snapshot files, and removes the sandbox record from the database. **This cannot be undone.** Persistent volumes attached to the sandbox are detached but **not** deleted — their data survives. Use [`bhatti volume delete`](/docs/reference/cli/volumes/delete/) to remove a volume. Published URLs are automatically cleaned up. By default, destroy prompts for confirmation. In a non-interactive shell without `-y`, the command refuses to proceed and exits `1`. Pass `-y` / `--yes` to skip the prompt. ## Examples ```bash bhatti destroy dev ``` ```text Destroy sandbox "dev"? [y/N]: y sandbox/dev destroyed ``` ```bash # Non-interactive (CI / scripts) bhatti destroy dev -y ``` ```bash # Tear down everything you own bhatti list --json | jq -r '.[].name' | xargs -I{} bhatti destroy {} -y ``` ## Options \| Flag | Default | Description | | ---- | ------- | ----------- | | `-y`, `--yes` | `false` | Skip the interactive confirmation. Required for non-interactive use. | See [Global flags](/docs/reference/cli/#global-flags) for `--url`, `--token`, `--json`, `--timing`, `--data-dir`. ## Exit codes \| Code | Meaning | | ---- | ------- | | `0` | Destroyed. | | `1` | Sandbox not found, or non-interactive shell without `-y`. | ## See also * [`bhatti stop`](/docs/reference/cli/sandbox/stop/) — pause without destroying (data preserved) * [`bhatti volume delete`](/docs/reference/cli/volumes/delete/) — detached volumes don't go with the sandbox * [API: `DELETE /sandboxes/:id`](/docs/reference/api/#destroy-a-sandbox) # bhatti edit > Update mutable settings on a sandbox. Currently keep-hot only. Update mutable settings on a running or stopped sandbox. ## Synopsis ``` bhatti edit --keep-hot bhatti edit --allow-cold ``` ## Description Currently the only mutable setting is the thermal-management toggle. Resource fields like `cpus` and `memory_mb` are immutable after creation — destroy and re-create instead. Setting `--keep-hot` on a stopped sandbox immediately wakes it, since "kept hot" implies running. Setting `--allow-cold` doesn't pause the sandbox; it just re-enables the thermal manager's automatic transitions. `--keep-hot` and `--allow-cold` are mutually exclusive. ## Examples ```bash # Pin an autonomous agent so it's never paused bhatti edit my-agent --keep-hot ``` ```bash # Re-enable automatic thermal transitions bhatti edit my-agent --allow-cold ``` ## Options \| Flag | Default | Description | | ---- | ------- | ----------- | | `--keep-hot` | `false` | Disable thermal transitions. Wakes the sandbox if it was stopped. | | `--allow-cold` | `false` | Re-enable thermal transitions. Does not actively pause the sandbox. | See [Global flags](/docs/reference/cli/#global-flags) for `--url`, `--token`, `--json`, `--timing`, `--data-dir`. ## See also * [`bhatti create --keep-hot`](/docs/reference/cli/sandbox/create/) — set this at creation * [Thermal states](/docs/under-the-hood/thermal-states/) — how hot/warm/cold transitions work * [API: `PATCH /sandboxes/:id`](/docs/reference/api/#update-a-sandbox) # bhatti inspect > Show full details for a single sandbox. Aliased as bhatti info. Show full details for one sandbox in `kubectl describe`-style output. Aliased as `bhatti info`. ## Synopsis ``` bhatti inspect bhatti info ``` ## Description Prints status, thermal state, image, creation/stop timestamps, resource allocation, network info, and `keep_hot`. For sandboxes in the `running` state, disk usage is queried live by running `df -m /` inside the guest — the row shows total / used / free in MB. krucible sandboxes report no guest IP, so the `IP` line is empty — reach a sandbox through a published URL or the reverse proxy, not a guest address. Stopped sandboxes show their static disk size; the live `df` query is skipped to avoid waking them. Use `--json` to get the full sandbox record as JSON for scripting. ## Examples ```bash bhatti inspect dev ``` ```text Name: dev ID: a1b2c3d4 Status: running Thermal: hot Image: minimal Created: 2026-04-29T15:21:48Z Resources: CPUs: 2 Memory: 1024 MB Disk: 8192 MB (1247 MB used, 6945 MB free) Network: IP: ``` ```bash bhatti info agent --json | jq .keep_hot ``` ```text true ``` ```bash # Stopped sandbox — no live df, static disk size shown bhatti inspect scraper ``` ```text Name: scraper ID: e5f6g7h8 Status: stopped Thermal: cold Image: browser Created: 2026-04-22T10:14:03Z Stopped: 2026-04-29T08:55:12Z Resources: CPUs: 1 Memory: 2048 MB Disk: — (stopped) Network: IP: ``` ## Options This command takes only global flags. See [Global flags](/docs/reference/cli/#global-flags) for `--url`, `--token`, `--json`, `--timing`, `--data-dir`. ## See also * [`bhatti list`](/docs/reference/cli/sandbox/list/) — list all sandboxes * [`bhatti edit`](/docs/reference/cli/sandbox/edit/) — change mutable settings * [API: `GET /sandboxes/:id`](/docs/reference/api/#get-a-sandbox) # bhatti list > List sandboxes you own. Adds a wide format and JSON output. List sandboxes owned by the authenticated user. Aliased as `bhatti ls`. ## Synopsis ``` bhatti list [-o wide] bhatti ls [-o wide] ``` ## Description Returns one row per sandbox with status, thermal state, and the first published URL (if any). The default columns are tuned for fitting in a normal terminal; `-o wide` adds CPUs, memory, disk, and the rootfs image. The `IP` column is empty under krucible: sandboxes have no host-visible guest IP — you reach a service inside one by publishing it (`bhatti publish`) or forwarding a host port to it (`bhatti forward`), not by dialing a guest address. The column is retained for output compatibility. Listing does not wake cold sandboxes — the server reads from its database, not from the engines. The `THERMAL` column shows the current state (`hot`, `warm`, `cold`). As a side effect, `list` refreshes the local sandbox-name completion cache (`/tmp/bhatti-completions-`). Tab-completing sandbox names will pick up new sandboxes after the first `list` call following a `create` from another machine. ## Examples ```bash bhatti list ``` ```text NAME STATUS THERMAL IP dev running hot agent running cold scraper stopped - ``` ```bash bhatti ls -o wide ``` ```text NAME STATUS THERMAL IP CPUS MEMORY DISK IMAGE dev running hot 1 1024 0 minimal agent running cold 2 4096 8192 browser scraper stopped - 1 1024 0 minimal ``` ```bash # Machine-readable output bhatti ls --json | jq '.[] | select(.status == "running") | .name' ``` ```bash # When at least one sandbox has a published URL, an extra URL column appears bhatti list ``` ```text NAME STATUS THERMAL IP URL api running hot https://api.bhatti.sh dev running hot ``` ## Options \| Flag | Default | Description | | ---- | ------- | ----------- | | `-o`, `--output ` | (default columns) | Output format. Currently only `wide` is supported. | See [Global flags](/docs/reference/cli/#global-flags) for `--url`, `--token`, `--json`, `--timing`, `--data-dir`. ## See also * [`bhatti inspect`](/docs/reference/cli/sandbox/inspect/) — full details for a single sandbox * [`bhatti ps`](/docs/reference/cli/exec/ps/) — sessions inside a sandbox * [API: `GET /sandboxes`](/docs/reference/api/#list-sandboxes) # bhatti start > Resume a stopped sandbox from its snapshot. Resume a stopped sandbox from its on-disk snapshot. The VM continues exactly where it left off — same processes, same memory, same network connections. ## Synopsis ``` bhatti start [--force] ``` ## Description Restores the VM from its on-disk snapshot bundle (`memory.img` + `checkpoint.bin` + `manifest.json`). Typical cold restore is sub-second (\~380ms measured on Apple Silicon); a warm sandbox — one whose memory is still resident — resumes in \~4ms. If a previous restore failed (for example because the underlying rootfs file was corrupt or the VMM helper couldn't be launched), the sandbox enters the `unknown` state and `bhatti start` refuses to retry by default. Pass `--force` to retry — useful after fixing the underlying issue. You normally don't need to call `start` explicitly. Any API request — `exec`, `file read`, an HTTP request to a published URL — wakes a cold sandbox transparently before executing. ## Examples ```bash bhatti start dev ``` ```text sandbox/dev started ``` ```bash # Retry after a previously failed restore bhatti start dev --force ``` ## Options \| Flag | Default | Description | | ---- | ------- | ----------- | | `--force` | `false` | Retry start even if the sandbox is in an `unknown` state from a previously failed restore. | See [Global flags](/docs/reference/cli/#global-flags) for `--url`, `--token`, `--json`, `--timing`, `--data-dir`. ## See also * [`bhatti stop`](/docs/reference/cli/sandbox/stop/) — snapshot and stop * [Thermal states](/docs/under-the-hood/thermal-states/) — automatic resume * [API: `POST /sandboxes/:id/start`](/docs/reference/api/#start-a-sandbox) # bhatti stop > Snapshot a sandbox to disk and free its memory. Snapshot a sandbox to disk and free its memory. The VM keeps its identity and on-disk state; resume with [`bhatti start`](/docs/reference/cli/sandbox/start/). ## Synopsis ``` bhatti stop ``` ## Description Pauses the VM, dumps memory + CPU state to disk, then frees the host RAM. Every `stop` takes a full memory snapshot; there are no diff snapshots. Stopped sandboxes use zero CPU and zero memory on the host. Their disk footprint includes the rootfs, attached volumes, and the snapshot bundle (`memory.img`, `checkpoint.bin`, `manifest.json`). In normal operation you don't need this — idle sandboxes pause and resume automatically per the thermal manager. Use `bhatti stop` to control thermal state explicitly when you know a sandbox will be idle for a while and want to free memory immediately. ## Examples ```bash bhatti stop dev ``` ```text sandbox/dev stopped ``` ```bash # Resume later bhatti start dev ``` ## Options This command takes only global flags. See [Global flags](/docs/reference/cli/#global-flags) for `--url`, `--token`, `--json`, `--timing`, `--data-dir`. ## See also * [`bhatti start`](/docs/reference/cli/sandbox/start/) — resume from snapshot * [`bhatti snapshot create`](/docs/reference/cli/snapshots/create/) — named snapshot you can resume into a *new* sandbox * [Thermal states](/docs/under-the-hood/thermal-states/) — automatic pause/resume * [API: `POST /sandboxes/:id/stop`](/docs/reference/api/#stop-a-sandbox) # Secrets > Encrypted secrets injected into sandboxes as environment variables. Secrets are key-value pairs encrypted at rest with [age](https://age-encryption.org/) and scoped to your user. Reference them by name when creating a sandbox; they’re decrypted server-side and injected as environment variables at boot. | Command | Description | | ------------------------------------------------------------- | ---------------------------------------------- | | [`bhatti secret set`](/docs/reference/cli/secrets/set/) | Create or update a secret. | | [`bhatti secret list`](/docs/reference/cli/secrets/list/) | List secret names (values are never returned). | | [`bhatti secret delete`](/docs/reference/cli/secrets/delete/) | Delete a secret. | ## Quick patterns [Section titled “Quick patterns”](#quick-patterns) ```bash # Store bhatti secret set OPENAI_KEY sk-... bhatti secret set DATABASE_URL "postgres://user:pass@db.internal/app" # Reference at create bhatti create --name agent --secret OPENAI_KEY --secret DATABASE_URL # List names (no values) bhatti secret list # Update (set with the same name overwrites) bhatti secret set OPENAI_KEY sk-...new-value # Delete (no confirmation prompt — be careful) bhatti secret delete OPENAI_KEY ``` ## How secrets work [Section titled “How secrets work”](#how-secrets-work) 1. **Encryption at rest.** Values are encrypted with an age key the server stores at `/var/lib/bhatti/age.key`. The key is generated on first server start. Plaintext is never written to disk. 2. **Scoped per user.** Each user has their own secrets. Other users can’t see them, list them, or reference them by name. 3. **Names only in `list`.** `bhatti secret list` returns metadata (name, created/updated timestamps). The encrypted value is never serialized to JSON output. 4. **Inject by name.** `bhatti create --secret API_KEY` decrypts `API_KEY` and adds it to the sandbox’s environment as `API_KEY=`. 5. **Update is idempotent.** Re-running `bhatti secret set NAME value` overwrites the previous value with no error. ## Env var priority inside a sandbox [Section titled “Env var priority inside a sandbox”](#env-var-priority-inside-a-sandbox) When a command runs inside a sandbox, env vars are resolved in this order (later wins): ```plaintext defaults (PATH, TERM, HOME, LANG) ↓ overridden by secrets (--secret on create) ↓ overridden by sandbox env (--env on create) ``` There is **no** per-`bhatti exec` env override. The exec endpoint doesn’t accept an `env` field; the env passed in the request is silently ignored. To inject env vars, do it at create time. ## Caveat: `--template` ignores `--secret` [Section titled “Caveat: --template ignores --secret”](#caveat---template-ignores---secret) When creating from a template, the server uses only the template’s pre-configured secret list. Request-side `--secret` flags are silently dropped. See [`bhatti create`](/docs/reference/cli/sandbox/create/) for the workaround. # bhatti secret delete > Delete a secret. Cannot be undone. Delete a secret. ## Synopsis ``` bhatti secret delete <name> [-y] ``` ## Description Removes the secret from the encrypted store. **Cannot be undone.** By default, prompts for confirmation. In a non-interactive shell without `-y`, the command refuses and exits `1`. Sandboxes that already have the secret resolved into their environment keep running with the value they got at boot. Future creates that reference the deleted secret will fail with `secret "<name>" not found`. ## Examples ```bash bhatti secret delete OLD_KEY ``` ```text Delete secret "OLD_KEY"? [y/N]: y deleted ``` ```bash # Non-interactive bhatti secret delete OLD_KEY -y ``` ## Options \| Flag | Default | Description | | ---- | ------- | ----------- | | `-y`, `--yes` | `false` | Skip confirmation. | See [Global flags](/docs/reference/cli/#global-flags) for `--url`, `--token`, `--json`, `--timing`, `--data-dir`. ## See also * [`bhatti secret list`](/docs/reference/cli/secrets/list/) — confirm the right name first * [API: `DELETE /secrets/:name`](/docs/reference/api/#delete-a-secret) # bhatti secret list > List secret names. Values are never returned. List your secrets by name. Values are never returned by the API. ## Synopsis ``` bhatti secret list ``` ## Description Returns metadata for each secret you own — name, created-at, updated-at — but never the encrypted value or the decrypted plaintext. Once a secret is set, the value can be replaced (with `bhatti secret set NAME new-value`) but not read back. This is intentional. The encryption-at-rest model assumes the value is read by the server only when injecting into a sandbox; exposing it via the API would defeat the point. ## Examples ```bash bhatti secret list ``` ```text OPENAI_KEY DATABASE_URL GITHUB_TOKEN ``` ```bash # JSON for scripting bhatti secret list --json ``` ```json [ {"name": "OPENAI_KEY", "created_at": "2026-04-15T10:00:00Z", "updated_at": "2026-04-29T14:22:00Z"}, {"name": "DATABASE_URL", "created_at": "2026-04-22T09:15:00Z", "updated_at": "2026-04-22T09:15:00Z"} ] ``` ## Options This command takes only global flags. See [Global flags](/docs/reference/cli/#global-flags) for `--url`, `--token`, `--json`, `--timing`, `--data-dir`. ## See also * [API: `GET /secrets`](/docs/reference/api/#list-secrets) # bhatti secret set > Create or update an encrypted secret. Create or update a secret. ## Synopsis ``` bhatti secret set <name> <value> ``` ## Description Encrypts the value with the server's age key and stores it under the given name, scoped to the authenticated user. Re-running with the same name overwrites the previous value with no error. The CLI takes the value as a positional argument. **There's no stdin variant** — long or multiline values pass through your shell history. For sensitive values, use a shell that doesn't record (`HISTCONTROL=ignorespace` + leading space) or pipe through a temp variable that you `unset` afterwards. ## Examples ```bash bhatti secret set OPENAI_KEY sk-abc123def456 ``` ```text ok ``` ```bash # Update is the same call bhatti secret set OPENAI_KEY sk-new-value ``` ```bash # Multiline / connection string bhatti secret set DATABASE_URL "postgres://user:pass@db.internal:5432/app?sslmode=require" ``` ```bash # Avoid shell history (bash with HISTCONTROL=ignorespace, note leading space) bhatti secret set OPENAI_KEY sk-abc123def456 ``` ## Options This command takes only global flags. See [Global flags](/docs/reference/cli/#global-flags) for `--url`, `--token`, `--json`, `--timing`, `--data-dir`. ## See also * [`bhatti create --secret`](/docs/reference/cli/sandbox/create/) — inject the secret into a sandbox at boot * [API: `POST /secrets`](/docs/reference/api/#create-or-update-a-secret) # Snapshots > Named VM snapshots that capture memory, CPU, and disk. Resume into a new sandbox at any time. A snapshot captures the entire VM state at a point in time — memory, CPU registers, disk, running processes, open file descriptors, network connections. Resume restores all of it, exactly. The new sandbox picks up where the original left off. | Command | Description | | ----------------------------------------------------------------- | ------------------------------------------ | | [`bhatti snapshot create`](/docs/reference/cli/snapshots/create/) | Checkpoint a running sandbox. | | [`bhatti snapshot list`](/docs/reference/cli/snapshots/list/) | List your snapshots. | | [`bhatti snapshot resume`](/docs/reference/cli/snapshots/resume/) | Resume from a snapshot into a new sandbox. | | [`bhatti snapshot delete`](/docs/reference/cli/snapshots/delete/) | Delete a snapshot. | ## How is this different from `bhatti stop`? [Section titled “How is this different from bhatti stop?”](#how-is-this-different-from-bhatti-stop) `bhatti stop` snapshots the *current* sandbox to disk and freezes it; `bhatti start` resumes that same sandbox. The sandbox doesn’t go away — it’s just paused. `bhatti snapshot create` saves a **named** copy of the VM state. The original sandbox keeps running. `bhatti snapshot resume` later creates a **new** sandbox from that state, with a new name and new IP. You can resume from the same snapshot multiple times to stamp out copies. | | `stop`/`start` | snapshot | | -------------------------- | ----------------------------- | -------------------------------- | | Original sandbox affected? | Yes — paused | No — keeps running | | Result of resume | Same sandbox awake | A *new* sandbox | | Multiple uses? | Once (it pairs with the stop) | Many — fork as often as you want | | Captures volumes? | No (volumes are detached) | No (rootfs + memory only) | ## Quick patterns [Section titled “Quick patterns”](#quick-patterns) ```bash # Checkpoint before a risky experiment bhatti snapshot create dev --name dev-pre-experiment # Fork: same VM state, multiple copies bhatti snapshot resume dev-pre-experiment --name copy-1 bhatti snapshot resume dev-pre-experiment --name copy-2 # Clean up bhatti snapshot delete dev-pre-experiment -y ``` ## What’s *not* in a snapshot [Section titled “What’s not in a snapshot”](#whats-not-in-a-snapshot) * **Persistent volumes.** Snapshots only capture the rootfs and memory; volumes are detached when you resume into a new sandbox. Re-attach them on the resumed sandbox if you need them. * **External state.** TCP connections to *other* hosts may be in an awkward state on resume — kernel state is preserved, but the remote peer doesn’t know the sandbox stopped and resumed. # bhatti snapshot create > Checkpoint a running sandbox to a named snapshot. Checkpoint a running sandbox to a named snapshot. ## Synopsis ``` bhatti snapshot create <sandbox> --name <snapshot-name> ``` ## Description Captures memory, CPU registers, disk, and running processes from the running sandbox and saves them under `<snapshot-name>`. The source sandbox is briefly paused during the capture (a few seconds — all snapshots are full; there are no diff snapshots), then resumes. The snapshot is stored on the server in the user's snapshot directory (`/var/lib/bhatti/snapshots/<user>/`). Resume into a new sandbox with [`bhatti snapshot resume`](/docs/reference/cli/snapshots/resume/) at any time. `--name` is required. Names must match `[a-zA-Z0-9][a-zA-Z0-9._-]{0,62}` — same rules as sandbox names. ## Examples ```bash bhatti snapshot create dev --name pre-experiment ``` ```text checkpoint "pre-experiment" created (512MB) ``` ```bash # Re-running with the same snapshot name overwrites the previous one bhatti snapshot create dev --name daily ``` ## Options \| Flag | Default | Description | | ---- | ------- | ----------- | | `--name <string>` | (required) | Snapshot name. | See [Global flags](/docs/reference/cli/#global-flags) for `--url`, `--token`, `--json`, `--timing`, `--data-dir`. ## See also * [`bhatti snapshot resume`](/docs/reference/cli/snapshots/resume/) — create a sandbox from this snapshot * [`bhatti stop`](/docs/reference/cli/sandbox/stop/) — pause the original instead of forking * [API: `POST /sandboxes/:id/checkpoint`](/docs/reference/api/#create-a-snapshot) # bhatti snapshot delete > Delete a snapshot. Cannot be undone. Delete a named snapshot. ## Synopsis ``` bhatti snapshot delete <snapshot-name> [-y] ``` ## Description Removes the snapshot record and the on-disk snapshot files (`memory.img`, `checkpoint.bin`, `manifest.json`, copied rootfs). **Cannot be undone.** By default, prompts for confirmation. In a non-interactive shell without `-y`, the command refuses and exits `1`. Sandboxes that were previously resumed from this snapshot are unaffected — they have their own copy of the state and don't reference the original. ## Examples ```bash bhatti snapshot delete pre-experiment ``` ```text Delete snapshot "pre-experiment"? [y/N]: y deleted ``` ```bash # Non-interactive bhatti snapshot delete pre-experiment -y ``` ## Options \| Flag | Default | Description | | ---- | ------- | ----------- | | `-y`, `--yes` | `false` | Skip confirmation. | See [Global flags](/docs/reference/cli/#global-flags) for `--url`, `--token`, `--json`, `--timing`, `--data-dir`. ## See also * [API: `DELETE /snapshots/:name`](/docs/reference/api/#delete-a-snapshot) # bhatti snapshot list > List named VM snapshots. List your named VM snapshots. ## Synopsis ``` bhatti snapshot list ``` ## Description Lists snapshots owned by the authenticated user. Each row includes the snapshot ID, name, source sandbox name, and on-disk size in MB. ## Examples ```bash bhatti snapshot list ``` ```text ID NAME SOURCE SIZE snap_a1b2c3d4 pre-experiment dev 512MB snap_e5f6g7h8 daily worker 1024MB snap_i9j0k1l2 ready base 384MB ``` ```bash # JSON for scripting bhatti snapshot list --json | jq '.[] | select(.source_sandbox == "base")' ``` ## Options This command takes only global flags. See [Global flags](/docs/reference/cli/#global-flags) for `--url`, `--token`, `--json`, `--timing`, `--data-dir`. ## See also * [API: `GET /snapshots`](/docs/reference/api/#list-snapshots) # bhatti snapshot resume > Resume a snapshot into a new sandbox. The original is unaffected. Resume a named snapshot into a new sandbox. ## Synopsis ``` bhatti snapshot resume <snapshot-name> [--name <new-sandbox-name>] ``` ## Description Creates a brand-new sandbox by restoring the snapshot's memory, CPU, and disk state. Running processes pick up exactly where they left off; open file descriptors are still open; in-memory data structures are intact. The new sandbox gets a new ID. **Persistent volumes are not re-attached** — re-attach them with the same `--volume` semantics if needed. `--name` is optional. Without it, a name is generated from the snapshot's source sandbox plus a random suffix. You can resume the same snapshot multiple times — each call produces a fresh sandbox with the same starting state. Useful for fork-and-experiment workflows. ## Examples ```bash # Resume into a sandbox with a specific name bhatti snapshot resume pre-experiment --name dev-restored ``` ```text abc123 dev-restored ``` ```bash # Auto-generated name (source sandbox + suffix) bhatti snapshot resume pre-experiment ``` ```bash # Stamp out parallel copies for an A/B test bhatti snapshot resume base --name worker-a bhatti snapshot resume base --name worker-b bhatti snapshot resume base --name worker-c ``` ## Options \| Flag | Default | Description | | ---- | ------- | ----------- | | `--name <string>` | auto-generated | Name for the new sandbox. | See [Global flags](/docs/reference/cli/#global-flags) for `--url`, `--token`, `--json`, `--timing`, `--data-dir`. ## See also * [`bhatti snapshot create`](/docs/reference/cli/snapshots/create/) — make the snapshot * [`bhatti snapshot list`](/docs/reference/cli/snapshots/list/) — find snapshot names * [API: `POST /snapshots/:name/resume`](/docs/reference/api/#resume-a-snapshot) # Volumes > Persistent ext4 volumes that survive sandbox destruction. Resize, clone, back up, restore. Persistent volumes are ext4 filesystems that survive sandbox destruction. Create one, attach it at sandbox creation with `--volume name:/mount`, destroy the sandbox — the data stays. | Command | Description | | --------------------------------------------------------------------------- | ------------------------------------------------------------ | | [`bhatti volume create`](/docs/reference/cli/volumes/create/) | Create a persistent volume. | | [`bhatti volume list`](/docs/reference/cli/volumes/list/) | List your volumes and current attachments. | | [`bhatti volume delete`](/docs/reference/cli/volumes/delete/) | Delete a volume. Requires it to be detached. | | [`bhatti volume resize`](/docs/reference/cli/volumes/resize/) | Grow a volume. Cannot shrink. | | [`bhatti volume clone`](/docs/reference/cli/volumes/clone/) | Point-in-time copy. Source must be detached. | | [`bhatti volume backup`](/docs/reference/cli/volumes/backup/) | Back up to S3-compatible storage. Requires server S3 config. | | [`bhatti volume backup-list`](/docs/reference/cli/volumes/backup-list/) | List backups for a volume. | | [`bhatti volume restore`](/docs/reference/cli/volumes/restore/) | Restore from a backup. Volume must be detached. | | [`bhatti volume backup-delete`](/docs/reference/cli/volumes/backup-delete/) | Delete a backup. | ## Detached state [Section titled “Detached state”](#detached-state) Four operations require the volume to be **detached** from any sandbox before they’ll run: `delete`, `resize`, `clone`, and `restore` all return a `409 Conflict` if the volume is currently attached. Detach by either destroying the sandbox using the volume, or stopping it with [`bhatti stop`](/docs/reference/cli/sandbox/stop/) (which also detaches volumes). `backup` does **not** check for attached state — it can run while the sandbox is mounted, but be aware the snapshot may be inconsistent if writes are in flight at backup time. Stop the sandbox first if you need a guaranteed-consistent backup. ## Quick patterns [Section titled “Quick patterns”](#quick-patterns) ```bash # Create + attach in one workflow bhatti volume create --name workspace --size 5120 bhatti create --name dev --volume workspace:/workspace # Grow on the fly bhatti volume resize workspace --size 10240 # Snapshot before a risky operation bhatti volume clone workspace --name workspace-pre-upgrade # Off-site backup (requires `backup` config block on server) bhatti volume backup workspace bhatti volume backup-list workspace ``` ## Backup configuration [Section titled “Backup configuration”](#backup-configuration) `backup`, `restore`, `backup-list`, and `backup-delete` require an S3-compatible storage backend configured on the server. Without it, they return `501 Not Implemented` with the message `backup not configured — add backup section to config.yaml`. See [Configuration: backup](/docs/reference/config/#backup). # bhatti volume backup > Back up a volume to S3-compatible storage. Requires server backup config. Back up a persistent volume to S3-compatible storage. ## Synopsis ``` bhatti volume backup <volume-name> ``` ## Description Compresses (zstd) the volume's ext4 file and uploads it to the S3-compatible bucket configured on the server. Returns a backup record with an ID, size, and S3 key. The volume **does not** need to be detached for this to run. However, if writes are in flight at backup time, the snapshot may be inconsistent. For a guaranteed-consistent backup, stop the sandbox first: ```bash bhatti stop dev # detaches volume, flushes guest writes bhatti volume backup workspace bhatti start dev # resume ``` Requires a `backup` block in the server's `config.yaml`. Without it, the API returns `501` with `backup not configured — add backup section to config.yaml`. See [Configuration: backup](/docs/reference/config/#backup). Backups are stored under the per-user S3 prefix configured by the server. Restore with [`bhatti volume restore`](/docs/reference/cli/volumes/restore/). ## Examples ```bash bhatti volume backup workspace ``` ```text backup bk_a1b2c3d4 created (workspace, 5234567890 bytes) ``` ```bash # JSON for scripting (capture the ID for later restore) backup_id=$(bhatti volume backup workspace --json | jq -r .id) echo "backed up: $backup_id" ``` ## Options This command takes only global flags. See [Global flags](/docs/reference/cli/#global-flags) for `--url`, `--token`, `--json`, `--timing`, `--data-dir`. ## See also * [`bhatti volume backup-list`](/docs/reference/cli/volumes/backup-list/) — see existing backups * [`bhatti volume restore`](/docs/reference/cli/volumes/restore/) — recover from a backup * [`bhatti volume clone`](/docs/reference/cli/volumes/clone/) — local point-in-time copy (no S3, no compression) * [API: `POST /volumes/:name/backups`](/docs/reference/api/#backup-a-volume) # bhatti volume backup-delete > Delete a backup from S3 and the local backup list. Delete a volume backup. ## Synopsis ``` bhatti volume backup-delete <volume-name> <backup-id> [-y] ``` ## Description Removes the backup record from the server and deletes the corresponding object in S3. Cannot be undone. By default, prompts for confirmation. In a non-interactive shell without `-y`, the command refuses and exits `1`. ## Examples ```bash bhatti volume backup-delete workspace bk_a1b2c3d4 ``` ```text Delete backup bk_a1b2c3d4? [y/N]: y deleted ``` ```bash # Non-interactive bhatti volume backup-delete workspace bk_a1b2c3d4 -y ``` ```bash # Trim to last 7 backups bhatti volume backup-list workspace --json \ | jq -r 'sort_by(.created_at) | reverse | .[7:][] | .id' \ | xargs -I{} bhatti volume backup-delete workspace {} -y ``` ## Options \| Flag | Default | Description | | ---- | ------- | ----------- | | `-y`, `--yes` | `false` | Skip confirmation. | See [Global flags](/docs/reference/cli/#global-flags) for `--url`, `--token`, `--json`, `--timing`, `--data-dir`. ## See also * [`bhatti volume backup-list`](/docs/reference/cli/volumes/backup-list/) * [API: `DELETE /volumes/:name/backups/:id`](/docs/reference/api/#delete-a-backup) # bhatti volume backup-list > List backups for a volume. List backups for a volume. ## Synopsis ``` bhatti volume backup-list <volume-name> ``` ## Description Lists backups recorded server-side for the given volume. The backups themselves live in S3; this command shows the metadata (ID, size, creation time) so you can pick one to restore. If there are no backups, prints `no backups`. ## Examples ```bash bhatti volume backup-list workspace ``` ```text ID SIZE CREATED bk_a1b2c3d4 4998 MB 2026-04-30T03:00:14Z bk_e5f6g7h8 5021 MB 2026-04-29T03:00:11Z bk_i9j0k1l2 4892 MB 2026-04-28T03:00:09Z ``` ```bash # Latest backup ID bhatti volume backup-list workspace --json | jq -r 'sort_by(.created_at) | last | .id' ``` ## Options This command takes only global flags. See [Global flags](/docs/reference/cli/#global-flags) for `--url`, `--token`, `--json`, `--timing`, `--data-dir`. ## See also * [`bhatti volume backup`](/docs/reference/cli/volumes/backup/) * [API: `GET /volumes/:name/backups`](/docs/reference/api/#list-volume-backups) # bhatti volume clone > Make a point-in-time copy of a volume. Source must be detached. Make a point-in-time copy of a persistent volume. ## Synopsis ``` bhatti volume clone <source-name> --name <new-name> ``` ## Description Server-side copy of the volume's ext4 file. The clone is independent — writes to the source after the clone do not affect the copy, and vice versa. The source volume must be **detached** when you clone it. If any sandbox has it mounted, the server returns `409 Conflict`. Detach by stopping or destroying the sandbox using it. Useful for: * Snapshotting before a risky upgrade or migration. * Forking a workspace for an experiment. * Pre-warming a base volume before stamping out workers. ## Examples ```bash # Snapshot before upgrade bhatti stop dev # detaches workspace bhatti volume clone workspace --name workspace-pre-upgrade bhatti start dev ``` ```text Cloned workspace → workspace-pre-upgrade ``` ```bash # Fork for an experiment bhatti volume clone workspace --name workspace-experiment bhatti create --name experiment --volume workspace-experiment:/workspace ``` ```bash # Source still attached — fails bhatti volume clone workspace --name copy ``` ```text 409 Conflict: source volume is attached — detach before snapshotting ``` ## Options \| Flag | Default | Description | | ---- | ------- | ----------- | | `--name <string>` | (required) | Name for the cloned volume. Must match the standard volume-name pattern. | See [Global flags](/docs/reference/cli/#global-flags) for `--url`, `--token`, `--json`, `--timing`, `--data-dir`. ## See also * [`bhatti volume backup`](/docs/reference/cli/volumes/backup/) — off-site copy via S3 (works while attached, but may be inconsistent) * [`bhatti stop`](/docs/reference/cli/sandbox/stop/) — detach by stopping the sandbox using it * [API: `POST /volumes/:name/snapshot`](/docs/reference/api/#clone-a-volume) # bhatti volume create > Create a persistent ext4 volume. Survives sandbox destruction. Create a persistent ext4 volume. ## Synopsis ``` bhatti volume create --name <name> --size <MB> ``` ## Description Allocates an ext4 filesystem image on the server, owned by the calling user. The volume is unattached at creation; mount it at sandbox creation time with [`--volume name:/mount`](/docs/reference/cli/sandbox/create/). The volume survives sandbox destruction. Multiple sandboxes can attach the same volume sequentially, but only one sandbox can have it attached at a time (read-only mounts via `name:/mount:ro` are the only way to share live). ## Examples ```bash bhatti volume create --name workspace --size 5120 ``` ```text vol_a1b2c3d4 workspace 5120MB ready ``` ```bash # Attach at sandbox create bhatti create --name dev --volume workspace:/workspace ``` ```bash # Attach read-only — multiple sandboxes can mount this simultaneously bhatti create --name reader --volume workspace:/data:ro ``` ## Options \| Flag | Default | Description | | ---- | ------- | ----------- | | `--name <string>` | (required) | Volume name. Must match `[a-zA-Z0-9][a-zA-Z0-9._-]{0,62}`. | | `--size <int>` | (required) | Initial size in MB. Grow later with [`bhatti volume resize`](/docs/reference/cli/volumes/resize/). | See [Global flags](/docs/reference/cli/#global-flags) for `--url`, `--token`, `--json`, `--timing`, `--data-dir`. ## See also * [`bhatti create --volume`](/docs/reference/cli/sandbox/create/) — attach at boot * [`bhatti volume resize`](/docs/reference/cli/volumes/resize/) — grow the volume * [API: `POST /volumes`](/docs/reference/api/#create-a-volume) # bhatti volume delete > Delete a persistent volume. Cannot be undone. Volume must be detached. Delete a persistent volume. **Cannot be undone.** ## Synopsis ``` bhatti volume delete <name> [-y] ``` ## Description Removes the volume record from the database and deletes the underlying ext4 file. The volume must be **detached** before deleting — if any sandbox has it mounted, the server returns `409 Conflict`. Stop or destroy the sandbox first, then retry. By default, prompts for confirmation. In a non-interactive shell without `-y`, the command refuses and exits `1`. ## Examples ```bash bhatti volume delete workspace ``` ```text Delete volume "workspace"? [y/N]: y deleted ``` ```bash # Non-interactive bhatti volume delete workspace -y ``` ```bash # Volume still attached bhatti volume delete workspace -y ``` ```text 409 Conflict: volume "workspace" attached to sandbox "dev" ``` ## Options \| Flag | Default | Description | | ---- | ------- | ----------- | | `-y`, `--yes` | `false` | Skip confirmation. | See [Global flags](/docs/reference/cli/#global-flags) for `--url`, `--token`, `--json`, `--timing`, `--data-dir`. ## See also * [`bhatti volume list`](/docs/reference/cli/volumes/list/) — find current attachments * [API: `DELETE /volumes/:name`](/docs/reference/api/#delete-a-volume) # bhatti volume list > List persistent volumes you own. List your persistent volumes. ## Synopsis ``` bhatti volume list ``` ## Description Lists volumes owned by the authenticated user. Each row includes ID, name, size, and current status. The default output is compact. Use `--json` to get the full record including current attachments (which sandbox has it mounted, where, and read-only flag). ## Examples ```bash bhatti volume list ``` ```text ID NAME SIZE STATUS vol_a1b2c3d4 workspace 5120MB ready vol_e5f6g7h8 data 20480MB ready ``` ```bash # Find which sandbox has a volume mounted bhatti volume list --json | jq '.[] | {name, attachments}' ``` ```json { "name": "workspace", "attachments": [ {"sandbox_id": "abc123", "mount": "/workspace", "read_only": false} ] } ``` ## Options This command takes only global flags. See [Global flags](/docs/reference/cli/#global-flags) for `--url`, `--token`, `--json`, `--timing`, `--data-dir`. ## See also * [API: `GET /volumes`](/docs/reference/api/#list-volumes) # bhatti volume resize > Grow a persistent volume. Cannot shrink. Grow a persistent volume. ## Synopsis ``` bhatti volume resize <name> --size <new-MB> ``` ## Description Resizes the underlying ext4 image file to the new size. The new size must be **larger** than the current size — shrinking is not supported. The volume must be **detached** before resizing. The server returns `409 Conflict` if any sandbox has it mounted. After the resize, attach it again and the new capacity is visible to the guest filesystem on next mount. ## Examples ```bash # Was 5120 MB; double it bhatti volume resize workspace --size 10240 ``` ```text resized to 10240MB ``` ```bash # Trying to shrink fails bhatti volume resize workspace --size 1024 ``` ```text 400 Bad Request: new size (1024MB) must be larger than current size (5120MB) ``` ```bash # Volume still attached bhatti volume resize workspace --size 10240 ``` ```text 409 Conflict: volume is attached — detach before resizing ``` ## Options \| Flag | Default | Description | | ---- | ------- | ----------- | | `--size <int>` | (required) | New size in MB. Must be larger than the current size. | See [Global flags](/docs/reference/cli/#global-flags) for `--url`, `--token`, `--json`, `--timing`, `--data-dir`. ## See also * [API: `POST /volumes/:name/resize`](/docs/reference/api/#resize-a-volume) # bhatti volume restore > Restore a volume from a backup. Volume must be detached. Restore a volume from a backup. ## Synopsis ``` bhatti volume restore <volume-name> --backup-id <id> ``` ## Description Downloads the backup from S3, decompresses it, and overwrites the volume's ext4 file with the backup's contents. **Existing data on the volume is replaced** — there's no merge. The volume must be **detached** before restoring. The server returns `409 Conflict` if any sandbox has it mounted. The backup ID comes from [`bhatti volume backup-list`](/docs/reference/cli/volumes/backup-list/). The restore is in-place: the volume keeps its name and any pending attachments resolve once it's done. ## Examples ```bash # Find a backup, restore from it bhatti stop dev bhatti volume backup-list workspace bhatti volume restore workspace --backup-id bk_a1b2c3d4 bhatti start dev ``` ```text volume restored from backup bk_a1b2c3d4 ``` ```bash # Volume still attached bhatti volume restore workspace --backup-id bk_a1b2c3d4 ``` ```text 409 Conflict: volume is attached to a sandbox — detach before restoring ``` ## Options \| Flag | Default | Description | | ---- | ------- | ----------- | | `--backup-id <string>` | (required) | The backup to restore from. Get one from [`backup-list`](/docs/reference/cli/volumes/backup-list/). | See [Global flags](/docs/reference/cli/#global-flags) for `--url`, `--token`, `--json`, `--timing`, `--data-dir`. ## See also * [`bhatti volume backup-list`](/docs/reference/cli/volumes/backup-list/) — find a backup to restore from * [`bhatti volume clone`](/docs/reference/cli/volumes/clone/) — make a clone before restoring, just in case * [API: `POST /volumes/:name/backups/restore`](/docs/reference/api/#restore-a-volume)