A moatbox space is a virtual machine on moatbox servers or on a server your team links. You reach it four ways: SSH through the moatbox gateway, an editor or agent app set up from the dashboard's Open in… menu, the moatbox CLI, or your own Tailscale tailnet. All of them start from an account at https://app.moatbox.dev and membership in the space's team. For the space itself, see Spaces.
SSH keys
The gateway knows you by your SSH public key. Add keys under Settings → General → SSH keys: paste one line from a file such as ~/.ssh/id_ed25519.pub. One key belongs to one moatbox account; adding a key that another account holds fails with 409. Revoke removes a key, and moatbox closes your open sessions within about 15 seconds.
The CLI setup command adds a key for you; see Set up the CLI.
The SSH gateway
Each space answers on the gateway under the SSH user <space>. moatbox gives it the alias moatbox-<space>. The block for ~/.ssh/config looks like this:
Host moatbox-my-space
HostName <gateway host>
Port <gateway port>
User my-space
IdentityFile "~/.ssh/id_ed25519"
IdentitiesOnly yes
Don't type it by hand. Get the exact block, with the gateway's host key for ~/.ssh/known_hosts, in one of these ways:
moatbox connect my-space --for ssh --writewrites both files for you.- Open in… → Terminal → Rather not install anything? Paste the SSH config by hand shows both.
GET /api/v1/spaces/my-space/sshreturns them as JSON (config,known_hosts,alias,host,port,user).
Copy SSH on the space's Overview page copies a plain ssh command instead.
Then:
ssh moatbox-my-space
scp ./notes.txt moatbox-my-space:/workspace/
rsync -av moatbox-my-space:/workspace/out/ ./out/
What the gateway relays
- interactive shells, one-off commands,
sftp, and soscpandrsync - local port forwards (
ssh -L)
The gateway refuses other channel types and declines remote forward requests, so ssh -R does not work.
Where you land
The space's workspace mode decides the user:
| Mode | SSH user inside the space | Notes |
|---|---|---|
| Full machine | dev |
In the VM, with passwordless sudo and Docker |
| Locked | agent |
In the gVisor sandbox container, no root |
Teams on the Team plan start locked; other plans start on full machine. Switch with Full machine on the space's Agents page or with moatbox space mode. On the Team plan, only owners and admins can turn full machine on. A team owner can forbid full machine. Switching closes open SSH sessions. Once someone has opened an SSH session in full machine mode, going back to locked takes a reset.
What happens on connect
- Connecting to a sleeping space wakes it. A space that is read-only, paused by moatbox or still starting refuses the session.
- moatbox records each session on the space's timeline: who opened it, the command line of a one-off command, bytes in and out, and the exit code. Shell output goes on the timeline too, unless monitoring is off for the space (see Privacy). Interactive sessions also emit the
run.startedevent. - Traffic on a session counts as activity and keeps the space awake. See Auto-stop.
- Every 15 seconds the gateway checks that your key, your team membership and the workspace mode still hold. If one changed, or the space went to sleep, it closes the connection.
Open in…
Open in… sits on the space's Overview page and under the space picker. It opens a side panel for the tool you pick. The menu lists editors and agent apps: VS Code, Cursor, Claude Code, Codex, opencode, T3 Code and a plain Terminal.
For tools that use SSH, the panel starts with Set up this computer once: a one-time command that installs the CLI and writes the SSH config (next section). Then it shows the tool's steps:
- VS Code: Open in VS Code opens
vscode://vscode-remote/ssh-remote+moatbox-<space>/workspace. It needs the Remote - SSH extension. Cursor works the same way with acursor://link. - Claude Code:
ssh moatbox-<space>thenclaude, and log in once from the URL it prints. Remote control needs a Claude subscription login. Runssh -t moatbox-<space> claude remote-controlby hand once to answer its questions, then keep it running withmoatbox connect <space> --for claude --remote-control. - Codex: turn on device code login in your ChatGPT settings, then
ssh moatbox-<space>,codex login --device-authandcodex. - Terminal:
ssh,scpandrsyncwith the alias.
The opencode and T3 Code entries start a server in the space from the panel. It listens on your tailnet, so the space needs to join it first; see Tailscale. For agent credentials in general, see Agent sign-in.
Set up the CLI
The Set up this computer once command looks like this:
curl -fsSL https://app.moatbox.dev/api/v1/cli.sh | sh -s -- mbx_cli_… my-space
It:
- downloads the
moatboxbinary for macOS (arm64, x86-64) or Linux (x86-64, arm64), checks its SHA-256, and installs it in~/.local/bin(setMOATBOX_BIN_DIRto change that) - creates
~/.ssh/moatbox_ed25519if it does not exist - runs
moatbox loginwith the code, naming this computer after the computer's name (setMOATBOX_LABELto change that) - runs
moatbox connect my-space --for ssh --write
The code works once and expires after 15 minutes. Press New command for another.
moatbox login
moatbox login --code mbx_cli_… [--api https://app.moatbox.dev] [--label my-laptop] [--ssh-key ~/.ssh/moatbox_ed25519.pub]
login trades the one-time code for an API key named "moatbox CLI on <label>" and adds the public key to your account. The key lasts 365 days and shows under Settings → API keys. The CLI saves it in ~/.config/moatbox/credentials.json with mode 600 (under $XDG_CONFIG_HOME when set).
For a pipeline, pipe a team API key in instead:
printenv MOATBOX_API_KEY | moatbox login --key-stdin
The key must start with mbx_. See API keys and CI.
Settings the CLI reads
| Variable | Use |
|---|---|
MOATBOX_API |
API base URL. Default: the one login saved, when the CLI uses the saved key; else https://app.moatbox.dev |
MOATBOX_API_KEY |
A team API key. Default: the key login saved |
MOATBOX_TEAM |
Team ID for moatbox new, when the key reaches several teams |
CLI commands
These commands talk to the hosted API. Run moatbox with no arguments for the full usage.
| Command | What it does |
|---|---|
moatbox login --code <code> / --key-stdin |
Sign this computer in |
moatbox connect <space> --for ssh|claude|codex [--write] [--remote-control] [-i <key>] |
Print the SSH config block, or write it with --write; prints the tool's login steps |
moatbox cp [-agent claude] <src> <dst> |
Copy files to, from or between spaces |
moatbox exec [--cwd <dir>] [--env NAME=value]... [--timeout 1h] <space> -- <command> [args...] |
Run a command in a space, stream its output, exit with its exit code |
moatbox new [--from <snapshot>] [--size small|medium|large|xlarge] [--ttl 30m] [--name <space>] [--wait 10m] |
Create a space, wait until it is up, print its name |
moatbox delete --yes <space> |
Move a space to the trash |
moatbox reset [--keep-logins] [--undo] <space> |
Snapshot a space and re-create its disk from the current base image |
moatbox net <space>, net allow|deny <space> <host>..., net mode <space> open|restricted |
Show or change the space's network allowlist |
moatbox space mode <space> [full|locked] |
Show or switch the workspace mode |
moatbox github connect <space> |
Hand the GitHub CLI's token on this computer to the team's GitHub integration; see Integrations |
moatbox ci ... |
List CI jobs and logs; see CI |
moatbox version |
Print the CLI version |
Details:
- connect looks for a key in
~/.ssh(moatbox_ed25519, thenid_ed25519,id_ecdsa,id_rsa) unless you pass-i.--writereplaces only theHost moatbox-<space>block in~/.ssh/config, makes no backup, and warns when an earlier line in the file overrides the block.--remote-controlgoes with--for claudeand runsclaude remote-controlin the space while the connection stays open. - cp takes a local path or
<space>:<path>on each side. The space path sits under/workspaceor/home/agent; a relative path means/workspace/<path>, and a trailing/keeps the source's name. Folders travel as tar, andcpskips symlinks inside a folder it uploads. Space-to-space copies go through moatbox. Transfers stop at 2 GiB. - new defaults to
--size medium, a name ofrun-plus random letters, and--wait 10m.--ttldeletes the space after that long, up to 30 days. - new needs an API key with
space.create. delete and--ttlneedspace.delete, orspace.delete-ownfor a space that key created. Thecipreset hasspace.createandspace.delete-own. The key frommoatbox login --codecannot create or delete spaces.
The same binary has more subcommands (run, pending, approve, deny, undo, log, timeline, serve, deploy and others). They work against a local moatbox daemon and its store, not the hosted API.
Tailscale
A space can join your Tailscale tailnet as a device named after the space. Use it to reach servers the space runs from your own devices.
Owners and admins set it up on the space's Network page, in the Tailscale card:
- In the Tailscale admin console, add
tag:moatboxtotagOwnersin the access controls. - Under Settings → Keys, generate an auth key: Reusable off, Ephemeral off, Pre-approved on, tagged
tag:moatbox. Tailscale removes an ephemeral device while the space sleeps. - Paste the key (
tskey-…) and press Join tailnet.
moatbox stores the key encrypted and never shows it again. Remove takes the space off the tailnet. Both actions wake a sleeping space. Over the API: PUT /api/v1/spaces/<space>/tailscale with {"auth_key": "tskey-…"}, GET to read the state, DELETE to leave.