# Authentication Source: https://docs.boxd.sh/cli/authentication Log in once through the browser, or mint API keys for CI, scripts, and agents. The CLI authenticates you once through the browser and stores the credentials locally. CI pipelines, scripts, and agents skip the browser with a token or a long-lived [API key](#api-keys). ## Log in ```bash theme={"theme":"github-dark"} boxd auth login # opens browser for one-time device login boxd auth # show your identity and the orgs you belong to boxd auth logout # remove stored credentials ``` Any command that needs auth auto-starts the login flow if no credentials are found, so on a fresh install you can run any command and log in on the way. ## Tokens Authenticate a single invocation with a token, overriding stored credentials: ```bash theme={"theme":"github-dark"} boxd --token= machine list # pass token directly BOXD_TOKEN= boxd machine list # or via env var ``` ## API keys For CI pipelines, scripts, or agent integrations where the browser login flow is not an option, mint long-lived API keys under `boxd auth keys`. The raw value is shown **only once**, at creation time. ```bash theme={"theme":"github-dark"} boxd auth keys create NAME # mint a new key, raw value to stdout boxd auth keys create NAME --expires-in-secs=86400 # with an expiry (1 day in this example) boxd auth keys create NAME --org myorg # fence the key to a specific org boxd auth keys create NAME --kind org # userless service credential (org admin only) boxd auth keys list # id, name, prefix, org, kind, last-used, expires (alias: ls) boxd auth keys remove ID # revoke a key (alias: rm) ``` Every key is fenced to **one org** and never grants account-wide access. Without `--org`, the key is fenced to your active org context. `--kind member` (the default) acts as you within that org. `--kind org` is a userless service credential limited to the org's shared machines, and creating one requires org admin. `keys create` writes the raw `bxd_…` value to stdout, while the warning, id, and expiry go to stderr. To put it straight into a GitHub secret without copy and paste: ```bash theme={"theme":"github-dark"} KEY=$(boxd auth keys create "gh-actions deploy owner/repo") gh secret set BOXD_API_KEY --repo owner/repo --body "$KEY" ``` The key never lands in shell history or scrollback. `--json` mode is also available for scripting: | Command | JSON shape | | ------------------------- | ---------------------------------------------------------------------- | | `auth keys create --json` | `{"id", "api_key", "expires_at"}` (`expires_at: 0` = no expiry) | | `auth keys list --json` | `[{"id", "name", "prefix", "org", "kind", "last_used", "expires"}, …]` | ## Org context Every account works in an org context, and your personal account counts as an organization of its own. Your active **context** decides which org a new machine is billed to and which machines `list` and `connect` see. ```bash theme={"theme":"github-dark"} boxd auth # identity + the orgs you belong to (active context marked) boxd auth switch acme # work in the "acme" org context (multi-word names need no quotes) ``` The active org context is a **per-device** selection. `boxd auth switch` writes it locally, and each surface (this CLI, the web console, other devices) keeps its own. Your own org is a normal org in the list, so you switch back to it by name like any other, and an unset context shows as `(default)`. To run one invocation in a specific org without switching, pass `--org`. Sharing machines with the org happens on the machine side. See [Organizations](/cli/commands#organizations) in the command reference. ## Next step You are logged in. Head to the full [command reference](/cli/commands). # Commands Source: https://docs.boxd.sh/cli/commands The full boxd CLI command reference, grouped by task. Every machine operation is available from the CLI, and all commands accept `--json` for scripting. This page is the full reference. ## The command tree The surface is a nested tree. This is the full first level: | Command | Alias | What it does | | ------------- | ------ | ---------------------------------------------------------------------------------------------------------------------------- | | `machine` | `m` | Machines end to end: create, fork, state control, exec, files, proxies, domains, checkpoints, networks, per-machine settings | | `new` | | Create a machine, from scratch, a fork, or a snapshot (shortcut for `machine new`) | | `connect` | | Open an interactive shell in a machine (shortcut for `machine connect`) | | `list` | `ls` | List your machines (shortcut for `machine list`) | | `snapshots` | `snap` | Reusable snapshots of memory and disk | | `auth` | | [Login](/cli/authentication), identity, org context, [API keys](/cli/authentication#api-keys) | | `manage` | | Org-level management: billing, integrations, wildcard domains | | `env` | | Env vars and secrets | | `config` | | Laptop-side CLI settings | | `docs` | | Open the boxd documentation in your browser | | `completions` | | Set up shell tab-completion | | `version` | | Show the CLI version | The three shortcuts exist because creating, connecting, and listing are what you do all day: `boxd new myapp` and `boxd machine new myapp` are the same command. `remove` is the delete verb everywhere (alias `rm`), and the common aliases are `ls` for `list`, `info` for `get`, and `ssh` for `connect`. `boxd --help` is the always-current reference for flags and subcommands, straight from your installed binary. Running bare `boxd` in a terminal drops into an interactive REPL (`boxd (org)>`) where you type the same commands without the `boxd` prefix, with tab completion and arrow-key pickers for missing arguments. ## Managing machines ```bash theme={"theme":"github-dark"} boxd machine new myapp # create a machine boxd machine new # auto-named boxd machine new myapp --auto-suspend-timeout=60 # idle secs before auto-suspend (0 disables) boxd machine new myapp --auto-hibernate-timeout=0 # never hibernate this machine boxd machine new myapp --shared # in an org context: shared with the whole org from birth boxd machine new myapp --networks=backend,cache # join these networks instead of the default one boxd machine new myapp --isolated # sandbox, see Networks below boxd machine list # list your machines (alias: ls) boxd machine get myapp # detailed info (status, image, auto-suspend; alias: info) boxd machine fork myapp myapp-v2 # fork with a full disk copy boxd machine fork myapp --auto-suspend-timeout=0 # fork with auto-suspend disabled boxd machine fork shared-vm --shared # keep a fork of a shared machine shared (default: private to you) boxd machine fork myapp --networks=backend # fork onto different networks (default: inherit the source's) boxd machine rename myapp myapp2 -y # rename (reboots to apply the hostname unless stopped) boxd machine remove myapp -y # destroy (requires -y or --confirm; alias: rm) ``` `boxd new myapp` and `boxd m ls` are the top-level and short forms. `machine new` folds in fork (`--fork`) and snapshot restore (`--from-snapshot`): ```bash theme={"theme":"github-dark"} boxd machine new myapp-v2 --fork myapp # same as `machine fork myapp myapp-v2` boxd machine new fresh --from-snapshot my-workspace # create from a snapshot (fast; restores memory + disk) ``` See [Suspend, resume, and hibernate](/guides/suspend-resume) for how the idle timers work. ### State control Three categories of state transition, all `boxd machine `: ```bash theme={"theme":"github-dark"} boxd machine start myapp # start a stopped machine boxd machine stop myapp # stop (disk persists; running processes lost) boxd machine pause myapp # standby (warm: memory preserved, sub-ms resume) boxd machine resume myapp # resume a paused machine boxd machine hibernate myapp # cold snapshot to disk now (frees host RAM) boxd machine wake myapp # wake a hibernated machine boxd machine reboot myapp # reboot (cold: memory lost, ~2s) ``` * **pause / resume** are warm. They freeze the VMM process and keep memory, running processes, and open sockets intact. Resume is sub-millisecond. This is the same mechanism as auto-suspend, only user-triggered. Status becomes `standby`. * **reboot** and **stop** + **start** are cold. They kill the VMM process, so memory is lost and the restart takes \~2s. Use `pause` for cost savings without losing state. Use `reboot` when you need a cold restart. ## Running commands ```bash theme={"theme":"github-dark"} boxd machine exec myapp -- uname -a # run a command boxd machine exec myapp -- 'cd /app && npm start' # shell constructs work boxd machine exec myapp -e API_KEY=secret -e DEBUG=1 -- CMD # env vars boxd machine exec myapp --timeout 30 -- CMD # timeout in seconds boxd machine exec myapp --tty -- htop # allocate a pseudo-TTY (for interactive tools) boxd machine exec myapp --json -- echo hello # JSON: {"output":"hello\n","exit_code":0} ``` Exit codes are forwarded, so a remote command that exits 42 makes `boxd machine exec` exit 42. The remote command's stdout and stderr are surfaced separately on the local side, so shell redirects work as you'd expect: ```bash theme={"theme":"github-dark"} boxd machine exec myapp -- gcc -v 2>/dev/null # drop the version banner (it's on stderr) boxd machine exec myapp -- cargo build 2>build.log # only warnings/errors land in build.log ``` PTY execs (`--tty`) are the exception. The kernel TTY layer merges stderr into stdout, as terminals do, so everything arrives on local stdout and `2>` filters won't separate them. The `--` separator between the machine name and the command is optional, since everything after the machine name is treated as the command. Put exec's own flags (`-e`, `--timeout`, `--tty`) before the command, and use `--` or quotes when your command starts with a dash. ## Interactive access `boxd connect` drops you straight into an interactive shell on the machine over the boxd API. It needs neither SSH config nor host keys, auto-resumes paused or hibernated machines on demand, and forwards exit codes. Type `exit` or Ctrl-D to come back to your local shell. ```bash theme={"theme":"github-dark"} boxd connect myapp # drops you into a shell (top-level shortcut) boxd machine connect myapp # the full path boxd machine ssh myapp # `ssh` is the alias of connect ``` `connect` requires an interactive terminal and refuses to run in scripts, pipes, or other non-TTY contexts. For automation, use `boxd machine exec` instead: ```bash theme={"theme":"github-dark"} boxd machine exec myapp -- uptime # one-shot, scriptable ``` ## Editor & SSH integration The CLI keeps a managed block of `Host` entries in `~/.ssh/config` **automatically**. Every `machine new`, `fork`, `list`, `remove`, and `rename` refreshes it, and a background agent re-syncs it periodically, so your SSH config stays current as machines come and go. There is no separate command to run. After the first sync, `.boxd` is reachable to plain `ssh`, `scp`, `rsync`, Cursor / VS Code Remote-SSH, JetBrains Gateway, Zed Remote, and anything else that reads SSH config: ```bash theme={"theme":"github-dark"} ssh myapp.boxd # plain SSH (alias carries HostName + Port) cursor --remote ssh-remote+myapp.boxd /home/boxd # Cursor (Remote-SSH) code --remote ssh-remote+myapp.boxd /home/boxd # VS Code # JetBrains Gateway / Zed: pick `myapp.boxd` from their host picker ``` Each machine is reachable on a dedicated SSH **port** (10000-30000 range) on the shared proxy IP, so each stanza carries a `HostName`, a `Port`, a `User`, and a managed `IdentityFile`. The CLI also pre-trusts the proxy's host key in `~/.ssh/known_hosts`, so the first connect never prompts. Connecting to the bare `.boxd.sh` hostname on port 22 reaches the proxy rather than the machine, so always use the `.boxd` alias. The managed block is bracketed with `# BEGIN boxd` / `# END boxd`, and nothing outside the markers is ever modified. ## Copying files Paths after `:` are relative to `/home/boxd` unless starting with `/`. Uploads stream automatically, with no inherent file-size cap. ```bash theme={"theme":"github-dark"} boxd machine cp ./local.txt myapp:/home/boxd/remote.txt # upload boxd machine cp myapp:/home/boxd/remote.txt ./local.txt # download boxd machine cp myapp:/path/file - # download to stdout echo data | boxd machine cp - myapp:/path/file # upload from stdin boxd machine cp -r ./dir myapp:dir # recursive ``` ## Proxy management Every machine gets `https://name.boxd.sh` forwarding to its default port. Publish more ports, either HTTPS subdomains or raw TCP/UDP forwards, all under `boxd machine proxy`. ```bash theme={"theme":"github-dark"} boxd machine proxy list --vm myapp # list published ports (alias: ls) boxd machine proxy add api --vm myapp --port 3001 # HTTPS subdomain: api.myapp.boxd.sh → 3001 boxd machine proxy set-port --vm myapp --port 3000 # change the default proxy's target port boxd machine proxy set-port --vm myapp --port auto # auto-detect the listening port boxd machine proxy remove api --vm myapp # remove a subdomain proxy (alias: rm) ``` Subdomain proxies are HTTPS-only. To expose a database, an SSH daemon, or any non-HTTP service, use a raw port forward (below). ## Custom domains Bring your own domain instead of `name.boxd.sh`. See [Custom domains](/guides/custom-domains) for the full walkthrough (DNS records, confirmation step, gotchas). ```bash theme={"theme":"github-dark"} boxd machine domain add app.example.com --vm myapp # prints DNS records, then waits for Enter (or pass -y/--confirm) boxd machine domain list # domain, machine, status, error (alias: ls) boxd machine domain remove app.example.com # unbind (alias: rm) ``` Org admins can delegate a wildcard to the whole org instead of binding machines one at a time: ```bash theme={"theme":"github-dark"} boxd manage domain set preview.mysaas.com # prints NS records to delegate, then waits for Enter (or -y/--confirm) boxd manage domain get # any org member can read it boxd manage domain clear # admin only ``` ## Raw TCP and UDP ports Raw forwards live under the same `machine proxy` group, selected with `--raw`. boxd opens a raw TCP or UDP port on the machine's public proxy and forwards it straight to a port inside the machine, for anything that isn't HTTP (databases, game servers, custom protocols). The public port is allocated for you. Connect on the machine's existing `name.boxd.sh` endpoint at that port. See [Port forwarding](/guides/port-forwarding) for the concept. ```bash theme={"theme":"github-dark"} boxd machine proxy add --vm myapp --port 5432 --raw # forward a public port -> :5432 (TCP) boxd machine proxy add --vm myapp --port 9999 --raw --udp # UDP instead boxd machine proxy add --vm myapp --port 7777 --raw --tcp --udp # both protocols on one allocated public port boxd machine proxy list --vm myapp # raw forwards show as kind tcp/udp boxd machine proxy remove 5432 --vm myapp # remove a raw forward by its machine port ``` Up to **3** raw forwards per machine. Re-adding a port you already forwarded keeps the same public port and just updates the protocol set. Forwards are owner-only and removed automatically when the machine is destroyed. ## Snapshots A snapshot captures a running machine's memory and disk at a moment in time, replicated across a few workers. Create a machine from it near-instantly with `machine new --from-snapshot`. ```bash theme={"theme":"github-dark"} boxd snapshots save myapp my-workspace # save memory + disk as a named snapshot (machine keeps running) boxd snapshots list # name, version, status, size, used (alias: ls) boxd snapshots remove my-workspace -y # delete a snapshot and its replicas (alias: rm) boxd snap save myapp my-workspace # `snap` is the short alias ``` Re-saving an existing name captures a new version, and the latest version is what new machines get. Snapshots are fenced to the org context that created them and never cross an org boundary. ## Checkpoints A checkpoint captures a running machine's memory and disk on the machine's own worker, restorable **in place**: the machine reboots into that exact state with the same name, URL, and ports. Checkpoints are per-machine, live and die with the machine, and never become a reusable image. Use them as quick save points before a risky change. ```bash theme={"theme":"github-dark"} boxd machine checkpoint save myapp before-migration # capture memory + disk (machine keeps running) boxd machine checkpoint list myapp # name, status, size, created, available (alias: ls) boxd machine checkpoint restore myapp before-migration # reboot the machine into the checkpoint (-y to skip confirm) boxd machine checkpoint remove myapp before-migration # delete a checkpoint (-y; alias: rm) ``` Up to 10 checkpoints per machine. `available: no` means the checkpoint's worker is no longer the machine's worker (for example after a migration), so it can't be restored until they line up again. ## Integrations Connect third-party accounts (**GitHub**, **Linear**, and **Slack**) under `boxd manage integrations`, and every personal machine in that org can use them. See [Integrations](/guides/integrations/overview) for the full model. ```bash theme={"theme":"github-dark"} boxd manage integrations # list connected integrations + what you can connect (alias: ls) boxd manage integrations connect linear # connect via browser OAuth: prints a URL, polls until you approve boxd manage integrations disconnect linear # revoke at the provider + remove from boxd boxd manage integrations mcp linear # install Linear's MCP into all agents (claude, codex, opencode) boxd manage integrations mcp slack claude # ...or only specific agents boxd manage integrations mcp linear --disable # remove the MCP from every agent boxd manage integrations --shared connect slack # connect the ORG's shared credential (org admin) ``` `connect` prints an authorize URL for you to open in a browser, and a human has to approve it. Integrations are fenced to one org. Your own connection serves your private machines in the active org, while `--shared` addresses the org's connection for its shared machines (admin only). ## Env vars & secrets Set environment variables and secrets once, and boxd injects them into every machine you own in that org. Plain vars are cleartext and readable back. `--secret` seals a value at rest, write-only. See [Env vars & secrets](/guides/env-secrets) for the full model (scopes, naming, injection). ```bash theme={"theme":"github-dark"} boxd env set DATABASE_URL postgres://… # cleartext env var boxd env set OPENAI_API_KEY sk-… --secret # sealed, write-only, never readable back boxd env list # NAME / VALUE / SCOPE; secrets show (sealed) (alias: ls) boxd env remove DATABASE_URL # delete (alias: rm; --scope disambiguates in an org context) boxd env scope OPENAI_API_KEY shared --from private # move a secret to another org scope (value preserved) ``` To put a literal **.env file** on **one** machine, rather than an account-wide var, use `boxd env push`: ```bash theme={"theme":"github-dark"} boxd env push myapp --file .env --dest ~/myapp/.env # upload + chmod 600 ``` `--file` defaults to `.env`, `--dest` to the file's name in the machine's home directory, and `--mode` to `600`. ## Billing Every machine is 2 vCPU / 8 GiB RAM / 100 GB disk. Your balance, usage, and payment live under `boxd manage billing`. ```bash theme={"theme":"github-dark"} boxd manage billing # your (or the active org's) balance and usage boxd manage billing --open # open the billing portal in your browser ``` See [boxd.sh/pricing](https://boxd.sh/pricing) for the rate card. ## Organizations Every account works in an org context, and your personal account counts as an organization of its own. Your active **context** decides which org a new machine is billed to and which machines `list` and `connect` see. Switching context is covered in [Org context](/cli/authentication#org-context). Sharing a machine opens it to every member of the org. ```bash theme={"theme":"github-dark"} boxd auth switch acme # work in the "acme" org context boxd machine share myapp # share a machine with the whole org (every member can reach it) boxd machine unshare myapp # stop sharing: private to you again (org keeps paying) ``` * In an org context, `boxd machine new` creates a machine **billed to the org but private to you**. Add `--shared` to make it visible to the whole org from birth. * `boxd manage billing` follows the active context, so in an org it shows the org's balance and usage. * **Share and unshare are owner-only.** Any member can `connect` to a *shared* machine. A private machine, personal or org-billed, is reachable only by its owner. * An org has three roles: **owner**, **admin**, and **member**. Owners and admins invite and remove members at [`/app/organizations`](https://boxd.sh/app/organizations). Sharing a machine **wipes its in-VM agent logins** (Claude Code, Codex, OpenCode) the instant it goes shared, so a teammate with a shell can never read your personal tokens. Unsharing restores Claude automatically, while Codex and OpenCode need a one-time re-login. Forking a shared machine gives you a **private** fork with your logins intact. See [VM sharing](/guides/share-a-vm) for the full handoff. ## Networks Machines reach each other when they **share at least one network**, or when **neither has any**. Machines with no networks form your default network, so naming a network opts a machine out of that pool. The same rule governs direct connections, the `.boxd` DNS name, and `machine exec` / `cp` between machines. ```bash theme={"theme":"github-dark"} boxd machine networks myapp # show current networks boxd machine networks myapp backend,cache # replace the whole set (not a merge) boxd machine networks myapp --clear # remove them all → the default network ``` Networks are labels you pick, with nothing to create first, and they never cross an organization. Changes take effect immediately, without a reboot. Set them at creation with `machine new --networks`. `machine new --isolated` sandboxes a machine. It never joins the default network and never reaches another isolated machine, and boxd strips everything that could reach into the rest of your account from it: the in-VM `boxd` CLI, your connected integrations, your saved agent logins, and the bridge to your laptop. Outbound internet, its HTTPS domain, and inbound SSH still work. Isolation is fixed at creation and always inherited by forks and snapshot restores, while its `--networks` remain editable and are the only thing it can reach through. See [Sandboxes](/use-cases/sandboxes). ## Per-machine settings Auto-suspend and auto-hibernate timeouts live under `boxd machine config`. You can also set them at creation with `--auto-suspend-timeout` / `--auto-hibernate-timeout`. ```bash theme={"theme":"github-dark"} boxd machine config list myapp # all settings for a machine boxd machine config get myapp auto-suspend.timeout # read one boxd machine config set myapp auto-suspend.timeout 300 # suspend after 5 min idle (0 = off) boxd machine config set myapp auto-hibernate.timeout 0 # never hibernate ``` ## CLI settings `boxd config` persists laptop-side CLI settings (kept in `config.toml`). ```bash theme={"theme":"github-dark"} boxd config list # api-url, client-utils.enable, daemon.enable boxd config get api-url # read one boxd config set api-url https://boxd.sh # persist the API server (flag/env still override) boxd config set client-utils.enable true # enable the clipboard/files/browser bridge for this device ``` Shell completions are covered in [Installation](/cli/installation#shell-completions). ## Open the docs ```bash theme={"theme":"github-dark"} boxd docs # opens https://docs.boxd.sh in your browser ``` ## Global flags | Flag | Description | | ---------------------------- | -------------------------------------------------------------------------------------- | | `--json` | Output as JSON (the only visible global besides `--help`) | | `--api-url` / `BOXD_API_URL` | API server URL (hidden; default `https://boxd.sh`) | | `--token` / `BOXD_TOKEN` | Auth token, overrides stored credentials (hidden) | | `--org` | Run one invocation in a specific org context (hidden; persist with `boxd auth switch`) | # Installation Source: https://docs.boxd.sh/cli/installation Install the boxd CLI and get your terminal talking to your machines. The boxd CLI manages machines from your local terminal. It talks to the boxd API directly, so it works without SSH keys, and it is the recommended surface for automation and coding agents.