guides
Remote lab happy path (host + laptop CLI)
Operator how-to: run grain on a sandbox host, dial it from your laptop with GRAIN_API, create a remote-coding lab, sync code, and tunnel published ports.
Run a durable coding sandbox on a Linux/macOS host, drive it from your laptop CLI. One pass, host then laptop.
For systemd, caps, reverse proxy, and team ops, see Remote sandbox host.
What you will have
| Machine | Role |
|---|---|
| Sandbox host | QEMU + grain daemon; VMs live here |
| Laptop | grain CLI with GRAIN_API / GRAIN_TOKEN; no local hypervisor required |
1. Host: install and config
Supported host: Linux or macOS with hardware virtualization (prefer Linux + KVM).
curl -fsSL https://raw.githubusercontent.com/cxdy/grain/main/scripts/install.sh | bash
# install QEMU if needed (grain doctor will say)
grain doctor
grain image pull grain-ubuntuWrite a config with a token and a safe API bind. Prefer loopback:
# ~/.grain/config.yaml (or /etc/grain/config.yaml for a service user)
api: 127.0.0.1:7474
api_token: "replace-with-long-random-secret" # openssl rand -hex 32| Bind | When | Requirements |
|---|---|---|
127.0.0.1:7474 | Default choice | Token still recommended; laptop uses ssh -L |
0.0.0.0:7474 (or LAN IP) | Direct LAN/VPN dial | Token required (daemon refuses without it) + host firewall to VPN/bastion only |
Never leave the API open to the public internet without token and network restriction (prefer TLS reverse proxy on loopback instead).
2. Host: start the daemon
grain up
# optional MCP on the host loopback only:
# grain up --mcp
# → http://127.0.0.1:7476/mcp (do not bind MCP to a public interface without protection)export GRAIN_TOKEN=replace-with-long-random-secret
curl -sS -H "Authorization: Bearer $GRAIN_TOKEN" http://127.0.0.1:7474/healthzgrain up / down / image * / doctor run on the host (or over SSH). They are not remote-CLI commands.
3. Create a durable lab
On the host, or from the laptop after step 4 is set up:
grain new --profile remote-coding --wait agent -n alice-devBuiltin remote-coding: persistent (-p), 4 CPU / 8 GiB / 32 GiB, image grain-ubuntu. No host mounts (laptop paths are not on the daemon machine).
Equivalent without the profile:
grain new -p -c 4 -m 8192 -d 32 -i grain-ubuntu --wait agent -n alice-dev4. Laptop: point CLI at the host
Preferred: tunnel the loopback API.
# terminal 1 — keep open
ssh -N -L 7474:127.0.0.1:7474 sandbox.example.com# terminal 2
export GRAIN_API=http://127.0.0.1:7474
export GRAIN_TOKEN=replace-with-long-random-secret
grain ls
grain sh alice-devIf the daemon already binds a reachable LAN URL (token + firewall in place):
export GRAIN_API=http://sandbox.example.com:7474
export GRAIN_TOKEN=replace-with-long-random-secret
grain lsTransport caveat: that path is cleartext HTTP. The Bearer token authenticates you, but anyone on the network path can sniff Authorization and request bodies. Prefer the SSH tunnel above, or put a TLS reverse proxy in front of 127.0.0.1:7474 and set GRAIN_API=https://sandbox.example.com (CLI uses the system TLS stack; no extra flags). The CLI prints a one-time stderr warning for non-loopback http:// URLs; set GRAIN_INSECURE_HTTP=1 only if you accept cleartext on that path.
Priority: --api flag > GRAIN_API > config api_url.
Install the same grain CLI on the laptop (no QEMU required for remote-only use).
5. Move code with sync (not -v from the laptop)
grain sync push ~/proj alice-dev:/work/proj
grain x alice-dev -- bash -lc 'cd /work/proj && make test'
grain sync pull alice-dev:/work/proj ~/proj| Do | Don’t |
|---|---|
grain sync push / pull for laptop ↔ guest trees | Assume -v /Users/you/... works from the laptop |
grain cp file alice-dev:/path for single files | Expect mounts of laptop paths on the remote host |
-v / mounts are paths on the sandbox host, not the laptop. A host-side share looks like -v /var/lib/grain/workspaces/alice:/work on the machine running the daemon. For laptop edit loops, use grain sync.
Sync and grain fs need a healthy guest agent (no scp fallback). Prefer --wait agent and the golden image grain-ubuntu.
6. Browser ports: host loopback + second tunnel
Published ports bind 127.0.0.1 on the sandbox host, not your laptop.
# create or add a forward (example: guest 3000 → host 3000)
grain new --profile remote-coding --wait agent -n web -P 3000:3000
# or on an existing VM: grain fwd add web 3000:3000
# laptop: tunnel that host loopback port
ssh -N -L 3000:127.0.0.1:3000 sandbox.example.com
# open http://127.0.0.1:3000
# or print ready-to-run lines for all published + live host ports
grain fwd tunnel web --host sandbox.example.com
# export GRAIN_SSH_HOST=sandbox.example.com # default for --hostYou can combine API and app tunnels:
ssh -N \
-L 7474:127.0.0.1:7474 \
-L 3000:127.0.0.1:3000 \
sandbox.example.com7. Agent deploy (remote CLI)
| Command | Remote CLI (GRAIN_API) |
|---|---|
ls, new, rm, stop, start, sh, x, cp, sync, fs, fwd, stats | Yes (agent ops via daemon proxy) |
grain agent deploy | Yes — daemon runs SSH deploy on the host (POST /vms/{name}/agent/deploy) |
up, down, image *, doctor, logs, proxy * | No — run on host |
The agent binary must exist on the daemon host (just agent-linux or ~/.grain/agent/grain-agent-linux-$arch), not on the laptop. Prefer golden grain-ubuntu with --wait agent so deploy is rarely needed.
8. Security (non-negotiable)
- Never open API or MCP without a shared secret and network controls.
- Prefer
api: 127.0.0.1:7474+ SSH tunnel (or TLS reverse proxy →https://…) over public binds. - Non-loopback
apirequiresapi_tokenor the daemon will not start. - Remote CLI to a non-loopback URL requires
GRAIN_TOKEN/api_token. - Cleartext Bearer is sniffable on non-loopback
http://— token ≠ encryption. Tunnel or terminate TLS. - CLI warns once on non-loopback cleartext
http://; silence only withGRAIN_INSECURE_HTTP=1when you accept the risk. - Keep MCP on
127.0.0.1unless you know how you will authenticate and firewall it. - One Bearer token ≈ one trust domain; this is a team lab pattern, not multi-tenant SaaS and not multi-user RBAC. Hostile co-tenants need separate OS users/
data_diror separate hosts (single-tenant model). - Guest agent ops go through the authenticated daemon proxy; agent hostfwd stays loopback-only — do not tunnel raw
:7475agent ports as a substitute for API auth. - Default networking is isolated SLIRP.
network: overlayis a shared L2: peers can control each other’s unauthenticated agents — only for cooperative labs (overlay security note).
Day-to-day cheat sheet
# host (once)
grain up
grain image pull grain-ubuntu
# laptop (each session)
ssh -N -L 7474:127.0.0.1:7474 sandbox.example.com # other terminal
export GRAIN_API=http://127.0.0.1:7474
export GRAIN_TOKEN=…
grain ls
grain new --profile remote-coding --wait agent -n alice-dev # once
grain sync push ~/proj alice-dev:/work/proj
grain sh alice-dev
grain stop alice-dev # free RAM; disk kept (persistent profile)
grain start alice-devSee also
- Remote sandbox host — systemd, firewall, reverse proxy, SDK patterns
- Profiles — builtin
remote-coding - Guest agent — exec, shell, sync requirements
- CLI reference —
sync, remote env vars - Configuration —
api,api_url,api_token - Security model