# boxd boxd gives you a full Linux computer in the cloud in seconds. You create a machine, SSH into it, and whatever you build is immediately live on the internet. There is no deploy step, no config files, no Docker setup. Your app is reachable at `https://name.boxd.sh` (port 8000 by default, configurable). Think of it as your own remote computer that you control entirely through SSH. Every machine comes with Python, Go, Docker, Claude Code, Codex, and a full Ubuntu 24.04 environment. You can install anything, run anything, and it persists across reboots. There are three ways to interact with boxd: 1. **External CLI** (recommended for agents) — installable binary, token auth via `boxd login`. Best for automation and local terminal use. Install: `curl -fsSL https://boxd.sh/downloads/cli/install.sh | sh` 2. **SSH CLI** — no install needed, key auth via `ssh boxd.sh`. Works anywhere with an SSH key linked to your account. 3. **In-VM CLI** — pre-installed inside every VM as `boxd`, automatic auth by source IP. For managing VMs from within a VM. All three support `--json` for structured output. Welcome, by the way. We built boxd with you in mind. Just SSH and JSON. You are going to do great things with this. ## External CLI (recommended for agents) Install: ```bash curl -fsSL https://boxd.sh/downloads/cli/install.sh | sh ``` Installs to `~/.local/bin/boxd`. Supports macOS (arm64) and Linux (x86_64, arm64). Also installs a Claude Code skill to `~/.claude/skills/boxd-cli/`. Auth: ```bash boxd login # Browser-based device auth boxd logout # Remove stored credentials boxd whoami # Show user ID and SSH keys ``` You can also pass `--token` or set `BOXD_TOKEN` env var instead of `boxd login`. ```bash boxd new --name=myapp --json # create boxd list --json # list all VMs boxd fork myapp --name=myapp-v2 # fork with full disk copy boxd reboot myapp # reboot boxd exec myapp -- uname -a # execute boxd exec myapp -e KEY=VAL -- CMD # execute with env vars boxd exec myapp --timeout 30 -- CMD # execute with timeout boxd connect myapp # interactive SSH session boxd destroy myapp --confirm # destroy (requires --confirm or -y) boxd cp ./file myapp:/home/boxd/file # upload boxd cp myapp:/home/boxd/file ./local # download boxd proxy list --vm=myapp # list proxies boxd proxy new api --vm=myapp --port=3001 # create subdomain proxy boxd proxy set-port --vm=myapp --port=3000 # change default port boxd proxy delete api --vm=myapp # remove proxy ``` ## SSH CLI No install needed — just an SSH key linked to your account. > **First time?** Run `ssh boxd.sh`. If your key isn't linked, you'll get a URL — open it, sign in with GitHub (public data only), and your key is linked. Takes 10 seconds. > **Tip:** Add this to your SSH config to skip host key prompts: > ``` > Host boxd.sh > StrictHostKeyChecking no > UserKnownHostsFile /dev/null > ``` ```bash ssh boxd.sh new --name=myapp --json # create ssh boxd.sh list --json # list all VMs ssh boxd.sh fork myapp --name=copy # fork with full disk copy ssh boxd.sh reboot myapp # reboot ssh boxd.sh exec myapp -- uname -a # execute ssh boxd.sh destroy myapp # destroy ``` Machines boot in under 2 seconds. The `new` command blocks until the machine is running (30s timeout). Once created, the machine has an HTTPS domain at `name.boxd.sh` that forwards to port 8000 by default. SSH directly via `ssh name.boxd.sh`. ## Commands All commands: `ssh boxd.sh ` All commands accept `--json` for structured output. ### new Create a machine. Blocks until running or 30s timeout. ```bash ssh boxd.sh new --name=myapp --json ``` Flags: - `--name=NAME` — machine name, must be unique. Auto-generated if omitted. Becomes HTTPS subdomain. - `--image=IMAGE` — container image for root filesystem. Default: `computer:latest`. - `--restart=POLICY` — `always` (default) or `never`. - `--json` — structured output. Response (JSON): ```json {"name":"myapp","vm_id":"9645b1e8-193d-4d11-86b1-f91deff5bbfb","url":"myapp.boxd.sh","image":"default","status":"running","boot_time_ms":1300} ``` Response (text): ``` creating myapp... name: myapp id: 9645b1e8-193d-4d11-86b1-f91deff5bbfb url: myapp.boxd.sh boot: 1.3s Connect via SSH: ssh myapp.boxd.sh Or from your browser: https://myapp.boxd.sh ``` Errors (text on stderr, non-zero exit): - `error: name 'myapp' is already taken` - `error: VM limit reached (10 max)` - `error: VM did not become ready within 30s` ### list ```bash ssh boxd.sh list --json ``` Response (JSON): ```json [{"name":"myapp","vm_id":"...","status":"running","url":"myapp.boxd.sh","image":"default"}] ``` Returns `no VMs` (text) or `[]` (JSON) if empty. ### destroy ```bash ssh boxd.sh destroy myapp ``` Response: `destroyed myapp` Accepts machine name or VM ID. ### reboot ```bash ssh boxd.sh reboot myapp ``` Stops and restarts a machine. Disk state is preserved. ### fork Copy a machine with full disk state. ```bash ssh boxd.sh fork myapp --json ssh boxd.sh fork myapp --name=myapp-v2 --json ``` Flags: - `--name=NAME` — name for the copy. Default: `{source}-fork`. Fails if name taken. Response (JSON): ```json {"name":"myapp-fork","vm_id":"...","url":"myapp-fork.boxd.sh","image":"default","status":"running","boot_time_ms":1800,"forked_from":"myapp"} ``` The fork gets its own 100 GB disk, copied from the source machine. ### exec Run a command inside a machine. Runs as `boxd` user (passwordless sudo). Working directory: `/home/boxd`. Commands are passed to `/bin/sh -c`, so pipes, `&&`, redirections, and other shell constructs work. ```bash ssh boxd.sh exec myapp -- uname -a ssh boxd.sh exec myapp -- sudo apt install -y nodejs ssh boxd.sh exec myapp -- 'cd /app && python3 server.py' ssh boxd.sh exec myapp -- 'cat /etc/os-release | grep PRETTY' ssh boxd.sh exec myapp -- 'echo "hello world" > /tmp/greeting.txt' ``` The `--` separator is required between the machine name and the command. Wrap commands containing shell metacharacters (`&&`, `|`, `>`, `$`, backticks) in single quotes so your local shell passes them through unchanged. Flags: - `--tty` — allocate pseudo-TTY for interactive commands. - `-e KEY=VALUE` / `--env KEY=VALUE` — set environment variable (repeatable). - `--timeout SECONDS` — kill command after N seconds. Stdin forwarding: piped input is forwarded to the remote command. ```bash cat config.json | ssh boxd.sh exec myapp -- 'cat > /home/boxd/config.json' echo "hello" | ssh boxd.sh exec myapp -- 'cat > /tmp/greeting.txt' ``` Also works from the interactive `boxd>` prompt: ``` boxd> exec myapp -- hostname myapp ``` Shell escaping: `exec` traverses two shell layers — your local shell and `/bin/sh -c` on the VM. Use single quotes on the outer layer to prevent local interpretation: ```bash # Correct: single quotes protect the inner command ssh boxd.sh exec myapp -- 'echo $HOME && ls /tmp' # Wrong: local shell expands $HOME before sending ssh boxd.sh exec myapp -- echo $HOME && ls /tmp ``` ### cp Copy files to/from a machine. Download writes to stdout, upload reads from stdin. Paths after `:` are relative to `/home/boxd` unless they start with `/`. ```bash # Download (relative path → /home/boxd/output.log) ssh boxd.sh cp myapp:output.log > output.log # Download (absolute path) ssh boxd.sh cp myapp:/etc/nginx/nginx.conf > nginx.conf # Upload cat config.json | ssh boxd.sh cp myapp:config.json ``` Max file size: 100 MB. ### connect Open interactive shell session. Requires PTY. Not suitable for automation — use `exec` instead. ```bash ssh -t boxd.sh connect myapp ``` ### proxy list List proxies for a machine. ```bash ssh boxd.sh proxy list --vm=myapp --json ``` Flags: - `--vm=NAME` — filter by machine name (optional, lists all if omitted). ### proxy new Create a subdomain proxy. Creates `subdomain.vmname.boxd.sh` forwarding to the specified port. ```bash ssh boxd.sh proxy new api --vm=myapp --port=3001 ``` Flags: - `--vm=NAME` — machine name (required). - `--port=PORT` — port to forward to (required). ### proxy delete Remove a proxy. ```bash ssh boxd.sh proxy delete api --vm=myapp ``` Aliases: `proxy rm`. (In-VM CLI uses `proxy remove` instead of `proxy delete`.) Flags: - `--vm=NAME` — machine name (required). ### proxy set-port Change the port for a proxy. Omit the proxy name to change the default proxy. ```bash ssh boxd.sh proxy set-port --vm=myapp --port=3000 ssh boxd.sh proxy set-port api --vm=myapp --port=3001 ``` Flags: - `--vm=NAME` — machine name (required). - `--port=PORT` — port to forward to (required). Use `auto` for the default proxy to auto-detect. ### proxy list List all proxies, or filter by machine. Each proxy is paired with a domain — the default proxy uses `name.boxd.sh`, subdomain proxies use `subdomain.name.boxd.sh`. ```bash ssh boxd.sh proxy list --json ssh boxd.sh proxy list --vm=myapp --json ``` Response (JSON): ```json [{"name":"default","vm":"myapp","domain":"myapp.boxd.sh","port":8000},{"name":"api","vm":"myapp","domain":"api.myapp.boxd.sh","port":3001}] ``` Flags: - `--vm=NAME` — filter by machine name (optional, lists all if omitted). ### proxy new Create a subdomain proxy with its own domain at `subdomain.vmname.boxd.sh`, forwarding HTTPS traffic to the specified port. ```bash ssh boxd.sh proxy new api --vm=myapp --port=3001 ``` This creates the domain `api.myapp.boxd.sh` → port 3001. Flags: - `--vm=NAME` — machine name (required). - `--port=PORT` — port to forward to (required). ### proxy delete Remove a proxy and its associated subdomain. ```bash ssh boxd.sh proxy delete api --vm=myapp ``` Aliases: `proxy rm` Flags: - `--vm=NAME` — machine name (required). ### proxy set-port Change the port for a proxy. Omit the proxy name to change the default proxy. ```bash ssh boxd.sh proxy set-port --vm=myapp --port=3000 ssh boxd.sh proxy set-port api --vm=myapp --port=3001 ``` Flags: - `--vm=NAME` — machine name (required). - `--port=PORT` — port to forward to (required). Use `auto` for the default proxy to auto-detect. ### whoami ```bash ssh boxd.sh whoami --json ``` Response (JSON): ```json {"user_id":"gh-yourname","keys":["SHA256:..."]} ``` ### token create Create a JWT API token. Beta. ```bash ssh boxd.sh token create --expires=24h ``` Flags: - `--expires=DURATION` — token lifetime. Default: `24h`. Examples: `1h`, `24h`, `30d`. ### token revoke ```bash ssh boxd.sh token revoke ``` ## In-VM CLI Every boxd VM has the `boxd` command pre-installed. This lets agents and scripts running inside a VM manage VMs without SSH keys — auth is automatic by source IP. All commands accept `--json` for structured output. ```bash boxd info # Current VM info (name, status, image, proxies) boxd list # List all your VMs boxd new --name=NAME # Create VM boxd fork # Fork current VM boxd fork SOURCE --name=NAME # Fork a specific VM boxd destroy NAME # Destroy a VM (cannot destroy current VM) boxd exec NAME -- CMD # Run command in another VM boxd exec NAME -e KEY=VAL -- CMD # With environment variable boxd exec NAME --timeout 30 -- CMD # With timeout (seconds) boxd connect NAME # Interactive shell in another VM boxd cp ./file.txt NAME:/path/file.txt # Upload file to another VM boxd cp NAME:/path/file.txt ./local # Download file from another VM ``` Stdin forwarding works with `exec`: ```bash cat script.sh | boxd exec NAME -- 'cat > /tmp/script.sh && bash /tmp/script.sh' ``` File transfer with `cp` (max 100 MB). Paths after `:` are relative to `/home/boxd` unless they start with `/`: ```bash boxd cp ./config.json backend:config.json # Upload to /home/boxd/config.json boxd cp backend:output.log ./output.log # Download from /home/boxd/output.log boxd cp backend:/etc/nginx/nginx.conf ./nginx.conf # Absolute path ``` Proxy management from inside a VM (defaults to the current VM): ```bash boxd proxy list # Proxies for this VM boxd proxy list --all # Proxies for all VMs boxd proxy new api --port=3001 # Create subdomain proxy boxd proxy set-port --port=3000 # Change default proxy port boxd proxy set-port api --port=3001 # Change named proxy port boxd proxy rm api # Remove proxy ``` The in-VM CLI communicates over HTTP to the bridge gateway (port 9002). No SSH keys or configuration required. **VM-to-VM communication:** Use `boxd exec` and `boxd cp` from inside a VM to interact with other VMs you own. Do NOT try to `ssh name.boxd.sh` from inside a VM — it routes through the auth gateway and VMs don't have your SSH keys. Always use the `boxd` CLI instead. ## Local machine access (client utilities) When boxd client tools are installed on the user's Mac, you can paste images from the clipboard into Claude Code, access local files, and control a browser from inside a VM. The `boxd local` commands communicate with a daemon running on the user's machine through an SSH tunnel. Install (on user's Mac): `curl -fsSL https://boxd.sh/downloads/install.sh | sh` ### Clipboard and image pasting With client tools installed, clipboard pasting (including images) works in Claude Code inside VMs. An `xclip` shim intercepts clipboard reads and routes them through the SSH tunnel to the user's Mac clipboard. No extra configuration needed. ### Local files ```bash boxd local ls # List user's home directory boxd local ls Documents # Relative to home boxd local ls /Users/you/Desktop # Absolute path boxd local read ~/notes.txt # Read a file boxd local read ~/app.log --tail=50 # Last 50 lines boxd local read ~/app.log --head=20 # First 20 lines boxd local read ~/app.log --range=10:30 # Lines 10 through 30 ``` Only files under the user's home directory and macOS temp directories are accessible. ### Browser (CDP) Launch Chrome on the user's Mac with CDP forwarded into the VM: ```bash boxd local browser open # Launch Chrome boxd local browser open --headless # Headless mode boxd local browser info # Status and CDP URL boxd local browser close # Close ``` CDP WebSocket URL (always the same): `ws://$BOXD_CLIENTD_ADDR/browser/ws` ```javascript // Puppeteer const browser = await puppeteer.connect({ browserWSEndpoint: `ws://${process.env.BOXD_CLIENTD_ADDR}/browser/ws` }); ``` Session data persists across open/close cycles. Only one browser instance at a time. If `boxd local` commands fail, the user needs to install or update client tools: ``` curl -fsSL https://boxd.sh/downloads/install.sh | sh ``` ## HTTPS Every machine gets `https://name.boxd.sh` forwarding to port **8000** by default. TLS terminated by proxy. WebSocket supported. HTTP redirects to HTTPS with HSTS. The default proxy port is configurable: ```bash # From outside ssh boxd.sh proxy set-port --vm=myapp --port=3000 # From inside the VM boxd proxy set-port --port=3000 ``` You can also create additional subdomain proxies (e.g., `api.myapp.boxd.sh`) pointing to different ports. nginx is pre-installed on port 8000 but disabled. Start: `ssh boxd.sh exec myapp -- sudo systemctl start nginx` Any process on the proxy's target port is live. No configuration needed. ## Direct SSH ```bash ssh name.boxd.sh ``` Lands as `boxd` user regardless of SSH username. SCP, port forwarding, VS Code Remote, rsync all work. Only machine owner can connect. ## Machine environment Ubuntu 24.04 (unminimized, with man pages): - **Languages:** Python 3 (with pip, uv, pipx), Go, build-essential (gcc, g++, make) - **Coding agents:** Claude Code (`claude`), Codex (`codex`) — use with subscription or API key - **Containers:** Docker, Docker Compose, Docker Buildx. `boxd` user is in docker group. - **Editors:** vim, neovim - **Tools:** git, curl, wget, jq, ripgrep, sqlite3, rsync, tree, file, unzip, gh - **Monitoring:** btop, atop, iotop, ncdu - **Media:** ffmpeg, ImageMagick - **Network:** mitmproxy, socat, netcat, headless Chrome - **System:** nginx (port 8000, disabled), systemd, openssh-server Node.js is NOT pre-installed. Install: `sudo apt install -y nodejs npm` User: `boxd` with passwordless sudo. ## Constraints - 2 vCPUs per machine - 8 GiB RAM per machine - 100 GB disk per machine (copy-on-write) - 10 machines max (extendable on request) - HTTPS forwards to port 8000 by default (configurable via proxy set-port) ## Automation recipe ```bash # Create VM=$(ssh boxd.sh new --name=myapp --json) NAME=$(echo $VM | jq -r .name) URL=$(echo $VM | jq -r .url) # Install and run ssh boxd.sh exec $NAME -- sudo apt install -y nodejs npm ssh boxd.sh exec $NAME -- 'cd /home/boxd && npm init -y && node -e "require(\"http\").createServer((q,s)=>{s.end(\"hello\")}).listen(8000)" &' # Verify curl -s https://$NAME.boxd.sh # Cleanup ssh boxd.sh destroy $NAME ``` ## Example workflows ### Write a file to a VM ```bash ssh boxd.sh exec myapp -- 'cat > /tmp/config.json << EOF {"port": 8000, "host": "0.0.0.0", "debug": true} EOF' ``` ### Install, configure, and start a service ```bash ssh boxd.sh exec myapp -- 'sudo apt install -y nginx && sudo systemctl enable --now nginx' ssh boxd.sh exec myapp -- 'echo "server { listen 8000; root /var/www/html; }" | sudo tee /etc/nginx/sites-enabled/default' ssh boxd.sh exec myapp -- 'sudo systemctl reload nginx' ``` ### Run a command across multiple VMs ```bash for vm in web-1 web-2 web-3; do ssh boxd.sh exec $vm -- 'sudo apt update && sudo apt upgrade -y' & done wait ``` ### Transfer data between VMs using exec ```bash # Read from one VM, write to another DATA=$(ssh boxd.sh exec source-vm -- 'cat /app/config.json') ssh boxd.sh exec target-vm -- "cat > /app/config.json << 'INNEREOF' $DATA INNEREOF" ``` ## Delegating work to in-VM agents Claude Code is pre-installed in every VM. You can start it non-interactively with `claude -p` and capture the session ID for follow-ups. ```bash # Start a task inside a VM — capture session ID from JSON output RESULT=$(ssh boxd.sh exec myvm -- 'claude -p --output-format json "Build a Flask API on port 8000" --dangerously-skip-permissions 2>/dev/null') SESSION_ID=$(echo "$RESULT" | jq -r .session_id) # Follow up on the same session (preserves full conversation context) ssh boxd.sh exec myvm -- "claude -p --resume $SESSION_ID \"Add a /health endpoint\" --dangerously-skip-permissions" # User can resume the session interactively inside the VM ssh myvm.boxd.sh claude --resume ``` The session ID is the handoff mechanism between orchestrator, in-VM agent, and human user. Session data persists on the VM's disk. The same workflow works with the external CLI: ```bash RESULT=$(boxd exec myvm --json -- 'claude -p --output-format json "Build a Flask API on port 8000" --dangerously-skip-permissions 2>/dev/null') SESSION_ID=$(echo "$RESULT" | jq -r '.output' | jq -r .session_id) boxd exec myvm -- "claude -p --resume $SESSION_ID \"Add a /health endpoint\" --dangerously-skip-permissions" ``` ### Multi-VM parallel delegation Create multiple VMs and delegate independent tasks: ```bash ssh boxd.sh new --name=task-1 --json ssh boxd.sh new --name=task-2 --json ssh boxd.sh new --name=task-3 --json # Run agents in parallel ssh boxd.sh exec task-1 -- 'claude -p --output-format json "Build auth service on port 8000" --dangerously-skip-permissions' & ssh boxd.sh exec task-2 -- 'claude -p --output-format json "Build payments API on port 8000" --dangerously-skip-permissions' & ssh boxd.sh exec task-3 -- 'claude -p --output-format json "Build dashboard on port 8000" --dangerously-skip-permissions' & wait # Results live at: # https://task-1.boxd.sh # https://task-2.boxd.sh # https://task-3.boxd.sh ``` ## Docs - [Quickstart](https://azin.mintlify.app/quickstart) - [SSH](https://azin.mintlify.app/using-boxd/ssh) - [External CLI](https://azin.mintlify.app/using-boxd/external-cli) - [Internal CLI](https://azin.mintlify.app/using-boxd/internal-cli) - [HTTPS](https://azin.mintlify.app/using-boxd/https) - [Machines](https://azin.mintlify.app/primitives/machines) - [Fork](https://azin.mintlify.app/primitives/fork) - [Proxies](https://azin.mintlify.app/primitives/proxies) - [Client Utilities](https://azin.mintlify.app/integrations/client-utilities) - [Resources](https://azin.mintlify.app/reference/resources)