Use Moochy in cloud boxes
Coding agents increasingly run in hosted boxes: persistent VMs such as boat.dev, sandboxes such as E2B, Daytona, Modal, Morph, and Fly Machines, and GitHub Codespaces. Donors rent GPU machines on RunPod, Vast.ai, or Lambda. This page covers all three uses:
- Use donated tokens from a cloud box: your agent runs in a box and uses your project's donations.
- Donate from a cloud machine: an always-on donation with an API key.
- Donate your own GPU server: a model you run on a rented GPU.
Availability. Everything on this page works with the current app, except reaching a GPU server on another machine over TLS (section 3, option B), which is being built. Check with moochy --help.
AI agents inside a box: this page is also at https://moochy.dev/docs/boxes.md. Follow section 1 for your platform. You need an enrollment token from the maintainer; never ask for their keys or passphrases.
Install the app in a box#
Every platform below uses the same installer. It needs outbound HTTPS and puts moochy in ~/.local/bin:
curl -fsSL https://github.com/moochy-dev/moochy-cli/releases/latest/download/moochy-installer.sh | sh
CopyOn Ubuntu 23.10 and later (boat.dev, recent Lambda images), moochy run also needs the app installed system-wide with its AppArmor profile, as described in Run your agent safely. The release archives include the profile as apparmor/moochy.
Network: the app connects to moochy.dev on port 443 and to relay.moochy.dev on port 8443. Most platforms allow both by default. If you restrict outbound traffic with a list of domains, note that several platforms match domains on ports 80 and 443 only: allow the relay's IP addresses for port 8443 instead.
1. Use donated tokens from a cloud box#
How it works#
A box never gets a copy of your device's keys. Instead:
-
You create an enrollment token on your own machine (or on moochy.dev, Repositories → the project → Boxes). It is shown once.
Copymoochy box token create --repo owner/repo --ttl 24h --cap '$20' --max-boxes 5--ttl: how long the token can be used to enroll new boxes.--cap: the monthly limit for each box it enrolls, within your own limit as a member.--max-boxes: how many boxes it may enroll in total (default 1).
-
The box enrolls itself with that token:
MOOCHY_ENROLL=<token> moochy up --headless. The app creates the box's own keys inside the box. The box becomes a separate device: limited to that one project, with its own monthly limit and an expiry, and no power to accept donors, add members, or donate. -
You get an email each time a box enrolls.
-
You list and remove boxes like devices:
Copymoochy box list # boxes enrolled for your projects moochy box revoke d_… # one box: its keys stop working immediately moochy box token list # your enrollment tokens moochy box token revoke bt_… # a token and every box it enrolled
One box, one identity. Many platforms can fork, branch, or snapshot a running box. A copy carries the original box's keys, and Moochy refuses it: only one copy of a device can be connected at a time, and the app refuses to start when it notices it was moved to another machine. You get an alert. To use the copy, delete its Moochy home folder and enroll it again (with the same token, if it still has boxes left, or a new one).
Rules that follow from this:
- Enroll when the box starts, never in a template, image, or snapshot. Install the app in the template; run
moochy upwith the token only in the running box. Otherwise every box made from the template is a copy of the same identity. - Pass the token as a plain environment variable from the platform's secret store. Some platforms offer "secrets" that only appear inside HTTPS headers through their own proxy (E2B's
Secret.fill, Daytona's Secrets); those do not work for Moochy. - Tokens and boxes expire. A short
--ttllimits the damage if a token leaks, and revoking a box stops it at once.
Sandboxing inside a box#
- In a full VM (boat.dev, E2B, Morph, Lambda, Daytona and Modal VM classes):
moochy run -- <agent>works as on any Linux machine. Runmoochy doctorfirst; it says what is missing, such as the Ubuntu AppArmor profile. - In a container or a gVisor sandbox where user namespaces or Landlock are not available (Modal's default runtime, some container platforms):
moochy doctornames what is missing. If the box is used only for this agent,moochy run --box-is-sandbox -- <agent>treats the box itself as the sandbox. The agent still gets a clean environment, the per-run token, and hidden secret files, and the app prints a warning. Tool calls from donated tokens reach such a session only if the project allows platform sandboxes (Project settings; on by default for box devices).
Lifetimes and idle timers#
Platforms stop boxes on their own schedule, and a background process often does not count as activity. When a box pauses and resumes, Moochy reconnects by itself; when a box is deleted, revoke it (or let it expire).
boat.dev#
Persistent Ubuntu 24.04 VMs with systemd, Docker, and disk-level fork.
boat env set-var base MOOCHY_ENROLL=<token> # injected into sandboxes using the "base" environment
boat new --setup-file ./setup.sh # add --no-auto-stop to keep it running (paid plans)
Copysetup.sh:
#!/bin/sh
set -eu
curl -fsSL https://github.com/moochy-dev/moochy-cli/releases/latest/download/moochy-installer.sh | sh
"$HOME/.local/bin/moochy" --home "$HOME/.moochy-box" up --headless
CopyThen, over boat ssh <id>: moochy --home ~/.moochy-box run --repo owner/repo -- claude.
- Sandboxes stop 1 hour after creation by default; processes do not survive stop and resume, so start the app again (or use a systemd unit).
boat forkand templates copy the disk, including the Moochy home folder: the fork must enroll again (delete~/.moochy-boxfirst). Environment variables carry into forks, so a fork can re-enroll with the same token while it has boxes left.
E2B#
Firecracker microVMs, created from templates. Templates are built by snapshotting a running sandbox, so install in the template, start in the sandbox.
// template.ts: install only; do not start moochy here
import { Template } from 'e2b'
export const template = Template()
.fromUbuntuImage('24.04')
.runCmd('curl -fsSL https://github.com/moochy-dev/moochy-cli/releases/latest/download/moochy-installer.sh | sh')
Copyimport { Sandbox } from 'e2b'
const sbx = await Sandbox.create('my-moochy-template', {
envs: { MOOCHY_ENROLL: process.env.MOOCHY_ENROLL }, // a plain env var, not an E2B Secret
timeoutMs: 60 * 60 * 1000,
})
await sbx.commands.run('~/.local/bin/moochy up --headless', { background: true })
Copy- The kernel has user namespaces, Landlock, and seccomp, so
moochy runcan sandbox the agent; check withmoochy doctor. - Sandboxes run up to 1 hour (Base) or 24 hours (Pro). With
onTimeout: 'pause', a resumed sandbox reconnects on its own. fork()and snapshots copy memory and disk: each copy must enroll again.- With a domain allow-list, port 8443 needs the relay's IP addresses allowed.
Daytona#
Containers by default, or VM sandboxes.
from daytona import Daytona, CreateSandboxFromImageParams, Image
image = Image.debian_slim("3.12").run_commands(
"curl -fsSL https://github.com/moochy-dev/moochy-cli/releases/latest/download/moochy-installer.sh | sh")
sandbox = Daytona().create(CreateSandboxFromImageParams(
image=image,
env_vars={"MOOCHY_ENROLL": token}, # env_vars, not Daytona Secrets
auto_stop_interval=0)) # a background process does not count as activity
sandbox.process.exec("~/.local/bin/moochy up --headless")
Copy- On the lower Daytona tiers, outbound traffic is limited to a fixed list of services and cannot be changed per sandbox; Moochy cannot connect there.
moochy doctorshows it. - In containers, check
moochy doctor; use--box-is-sandboxif user namespaces are missing. VM sandboxes support fork, which copies the identity: re-enroll.
Modal#
gVisor sandboxes by default, or VMs (runtime="vm").
modal secret create moochy-enroll MOOCHY_ENROLL=<token>
Copyimport modal
app = modal.App.lookup("moochy-agents", create_if_missing=True)
image = modal.Image.debian_slim().run_commands(
"curl -fsSL https://github.com/moochy-dev/moochy-cli/releases/latest/download/moochy-installer.sh | sh")
sb = modal.Sandbox.create(image=image, app=app, timeout=24 * 3600,
secrets=[modal.Secret.from_name("moochy-enroll")])
sb.exec("bash", "-lc", "~/.local/bin/moochy up --headless")
Copy- gVisor has no Landlock and only part of seccomp, so
moochy runreports what is missing: usemoochy run --box-is-sandbox(the gVisor sandbox is the boundary), or the VM runtime. - Sandboxes live at most 24 hours. Memory snapshots copy the identity: re-enroll.
outbound_domain_allowlistallows port 443 only; to restrict traffic and still reach the relay, useoutbound_cidr_allowlist.
Morph Cloud#
VM instances from snapshots, with branching.
snap = client.snapshots.create(vcpus=2, memory=4096, disk_size=16384, digest="moochy-1")
snap = snap.setup("curl -fsSL https://github.com/moochy-dev/moochy-cli/releases/latest/download/moochy-installer.sh | sh")
inst = client.instances.start(snap.id, ttl_seconds=3600)
inst.exec("MOOCHY_ENROLL='<token>' ~/.local/bin/moochy up --headless")
Copy- There is no secret store: pass the token at start, as above, never in a snapshot setup step.
branchmakes copies with memory and disk: each must enroll again.
Fly Machines#
Firecracker VMs from a Docker image; the root filesystem is fresh at every start.
FROM ubuntu:24.04
RUN apt-get update && apt-get install -y curl ca-certificates \
&& curl -fsSL https://github.com/moochy-dev/moochy-cli/releases/latest/download/moochy-installer.sh | sh
CMD ["/root/.local/bin/moochy", "--home", "/data/moochy", "up", "--headless", "--foreground"]
Copyfly volumes create moochy_data --size 1
fly secrets set MOOCHY_ENROLL=<token>
fly machine run . --volume moochy_data:/data
Copy- Keep the Moochy home on a volume so the box keeps one identity across restarts. A cloned machine gets a new, empty volume and enrolls as a new box.
GitHub Codespaces and devcontainers#
.devcontainer/devcontainer.json:
{
"image": "mcr.microsoft.com/devcontainers/base:ubuntu",
"postCreateCommand": "curl -fsSL https://github.com/moochy-dev/moochy-cli/releases/latest/download/moochy-installer.sh | sh",
"postStartCommand": "nohup ~/.local/bin/moochy up --headless > /tmp/moochy.log 2>&1 &",
"secrets": { "MOOCHY_ENROLL": { "description": "Moochy box enrollment token (moochy box token create)" } }
}
Copygh secret set MOOCHY_ENROLL --user # or for one repository: --app codespaces --repo owner/repo
Copy- The
secretsblock only reminds people to set the secret;gh secret setstores it. New secrets apply after the codespace restarts. - Codespaces stop after the idle timeout (30 minutes by default) and live at most 12 hours.
- Prebuilds share one image between many codespaces: install there, but enroll only in
postStartCommand, as above. - Inside the codespace's container, check
moochy doctor; use--box-is-sandboxif user namespaces are not available.
2. Donate from a cloud machine#
A donation from a cloud VM works like one from any server: follow Run Moochy on a server or in CI. In short:
read -rs MOOCHY_PASSPHRASE && export MOOCHY_PASSPHRASE
moochy --home /var/lib/moochy login --roles worker --headless
printf '%s' "$KEY" | moochy --home /var/lib/moochy keys add anthropic --key-stdin
moochy --home /var/lib/moochy safety --monthly-limit '$25' --accept-safety
moochy --home /var/lib/moochy service install --system
Copy- Use a separate API key with a spending limit at the provider (how), and keep the key and passphrase in the platform's secret store or a root-only file.
- The app locks itself down after start-up. In a container that blocks those system calls, it refuses to donate and
moochy doctorsays what the container runtime blocks; use a VM, or the container image. - Do not clone a donor machine. A copy has the same device keys and is refused. Give each machine its own
moochy login.
3. Donate your own GPU server#
You rent a GPU box, run a model server on it (vLLM, Ollama, llama.cpp, LM Studio), and donate tokens from it. See Donate from your own GPU for what is checked and why local usage is self-reported.
Option A (recommended): the app on the same machine#
Run the Moochy app on the GPU box itself, with the model server listening on 127.0.0.1. Nothing is exposed to the internet, no TLS or API key is needed, and this works today on every platform.
Sign in as in section 2 (login --roles worker --headless), then:
vllm serve Qwen/Qwen2.5-7B-Instruct --host 127.0.0.1 --port 8000 --max-num-seqs 4 &
moochy --home /var/lib/moochy keys add local --base-url http://127.0.0.1:8000 \
--model local/qwen2.5-7b=Qwen/Qwen2.5-7B-Instruct
moochy --home /var/lib/moochy config set slots_max 4
CopyThen finish as in section 2 (safety step, service). On RunPod and Vast.ai, which run containers, start both from the container's start script; on Lambda, use systemd units.
Option B: a GPU server you reach over TLS#
When the Moochy app runs elsewhere, the model server must be reachable over HTTPS with a valid certificate and an API key. You add it explicitly: the exact host name is allowed, the key is kept in your keystore and sent as an Authorization header, and the certificate is checked. Plain HTTP and hosts you did not add are refused.
printf '%s' "$SERVER_KEY" | moochy keys add local --base-url https://<host> --key-stdin \
--model local/qwen2.5-7b=Qwen/Qwen2.5-7B-Instruct
CopyStart the server with a key: vllm serve <model> --api-key "$SERVER_KEY". Ollama has no built-in key; put it behind a reverse proxy that checks one, or use option A.
| Platform | Stable HTTPS address | Notes |
|---|---|---|
| RunPod | Expose HTTP port 8000 in the pod; use https://<pod id>-8000.proxy.runpod.net |
The address is public and goes through Cloudflare, which gives up after 100 seconds without a first response: very long prompts on slow models can time out. Store SERVER_KEY in RunPod Secrets and reference it as {{ RUNPOD_SECRET_server_key }} |
| Vast.ai | A named Cloudflare tunnel on your own domain | The default trycloudflare.com links change and are not suitable. Direct IP and port also work if you add your own certificate |
| Lambda | Your own DNS name, a reverse proxy with a certificate (for example from Let's Encrypt), port 443 opened in Lambda's inbound rules | Lambda allows only inbound SSH by default; nothing else is exposed until you open it |
Platform summary#
| Platform | Kind | moochy run |
Copies of a box | Notes |
|---|---|---|---|---|
| boat.dev | Ubuntu 24.04 VM | Yes, with the AppArmor profile | fork, templates |
Auto-stop 1 h by default |
| E2B | Firecracker microVM | Yes | fork, snapshots, every template |
Up to 24 h; enroll in the sandbox, not the template |
| Daytona | Container, or VM | Container: check doctor; VM: yes |
VM fork |
Lower tiers cannot reach Moochy |
| Modal | gVisor, or VM | gVisor: --box-is-sandbox; VM: check doctor |
Memory snapshots | Up to 24 h; domain allow-list is port 443 only |
| Morph Cloud | VM | Check doctor |
branch |
No secret store |
| Fly Machines | Firecracker microVM | Check doctor |
machine clone (new empty volume) |
Keep the home on a volume |
| GitHub Codespaces | Container on a VM | Check doctor |
Prebuilds share an image | Up to 12 h |
| RunPod | Container | Donors only | — | No UDP; proxy times out after 100 s |
| Vast.ai | Container, or KVM VM | Donors only | — | Use a VM for systemd |
| Lambda | Ubuntu VM | Donors only | — | Inbound SSH only by default |
Platforms change; when this table and moochy doctor disagree, trust moochy doctor.
This page as Markdown: /docs/boxes.md · for AI agents: /llms.txt