Skip to content
All pages

Connect to a space

Reach a moatbox space over the SSH gateway, from your editor through Open in…, with the moatbox CLI, or over Tailscale.

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 --write writes 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/ssh returns 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 so scp and rsync
  • 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.started event.
  • 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 a cursor:// link.
  • Claude Code: ssh moatbox-<space> then claude, and log in once from the URL it prints. Remote control needs a Claude subscription login. Run ssh -t moatbox-<space> claude remote-control by hand once to answer its questions, then keep it running with moatbox connect <space> --for claude --remote-control.
  • Codex: turn on device code login in your ChatGPT settings, then ssh moatbox-<space>, codex login --device-auth and codex.
  • Terminal: ssh, scp and rsync with 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:

  1. downloads the moatbox binary for macOS (arm64, x86-64) or Linux (x86-64, arm64), checks its SHA-256, and installs it in ~/.local/bin (set MOATBOX_BIN_DIR to change that)
  2. creates ~/.ssh/moatbox_ed25519 if it does not exist
  3. runs moatbox login with the code, naming this computer after the computer's name (set MOATBOX_LABEL to change that)
  4. 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, then id_ed25519, id_ecdsa, id_rsa) unless you pass -i. --write replaces only the Host moatbox-<space> block in ~/.ssh/config, makes no backup, and warns when an earlier line in the file overrides the block. --remote-control goes with --for claude and runs claude remote-control in the space while the connection stays open.
  • cp takes a local path or <space>:<path> on each side. The space path sits under /workspace or /home/agent; a relative path means /workspace/<path>, and a trailing / keeps the source's name. Folders travel as tar, and cp skips 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 of run- plus random letters, and --wait 10m. --ttl deletes the space after that long, up to 30 days.
  • new needs an API key with space.create. delete and --ttl need space.delete, or space.delete-own for a space that key created. The ci preset has space.create and space.delete-own. The key from moatbox login --code cannot 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:

  1. In the Tailscale admin console, add tag:moatbox to tagOwners in the access controls.
  2. 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.
  3. 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.