Develop inside a sandbox
Turn a sandbox into a persistent development machine with an agent inside it that can spawn its own child sandboxes.
A sandbox can be your development machine. You keep a repository, a shell, and a coding agent inside it, and reach them from any device. This page is the setup we run, and the mistakes that cost us time.
This is the inverse of AI agents. There, an agent on your laptop drives Platinum. Here, the agent lives inside the sandbox.
What you get
- A persistent box. Stop and resume it; the disk survives.
- Terminals that outlive the browser, through Tabs.
- A coding agent that can spawn child sandboxes for heavy work.
- One scoped credential, so the agent cannot touch anything else you own.
Build the template
Do this once. debian-dev is not built in — templates are private to the
organization that built them, so a name someone else used is not a name you have.
Skipping this step is why -t debian-dev comes back as "template not found".
It carries what the rest of this page assumes: tmux (Tabs needs it for
terminals that survive a browser close), git, a couple of language runtimes, and
Claude Code.
pt api POST /v1/templates/from-spec -d '{
"name": "debian-dev",
"base_image": "docker.io/library/debian:12",
"default_cpu": 2, "default_ram_mb": 6144, "default_disk_gb": 20,
"size_mb": 5120,
"steps": [{"op": "run", "cmd": "export DEBIAN_FRONTEND=noninteractive; apt-get update && apt-get install -y --no-install-recommends tmux git curl ca-certificates less nano ripgrep openssh-client python3 nodejs npm && (npm install -g @anthropic-ai/claude-code || true) && apt-get clean && rm -rf /var/lib/apt/lists/*"}]
}'The build runs in the background — building → ready, a few minutes on a cold
base image. Watch it with pt template list, and boot nothing until it is
ready.
Change the step list to taste; it is an ordinary template. Add a Rust toolchain,
swap nodejs for a specific version, whatever your work needs. Just keep tmux
if you want Tabs.
Create the sandbox
Give it real resources. A coding agent plus a language toolchain needs headroom.
pt sandbox create -t debian-dev --cpu 2 --ram 6144 --disk 20 --name devWarning: size the disk for build output. A Rust target/ directory reached 13 GB on a 20 GB disk and filled it. A full disk breaks the terminal server, not just the build.
You can grow the disk later, so this is recoverable. See Grow the disk.
Grow the disk
Disk can grow at any time. It cannot shrink: the host grows the filesystem offline with resize2fs, and shrinking a populated filesystem risks data loss.
The sandbox must be stopped. The resize then cold-boots it.
pt sandbox stop <id>
pt sandbox resize <id> --disk 35Measured on a real box, 20 GB to 35 GB took about 30 seconds end to end, including the stop and the boot. Every file survived.
Warning: stopping ends every process in the sandbox. Terminal sessions are lost, though the disk and its files are not. Commit your work first.
Run the agent as a non-root user
Sandboxes give you a root shell. Claude Code refuses --dangerously-skip-permissions as root, so create an ordinary user and work there.
useradd -m -s /bin/bash dev
usermod -aG sudo dev
echo 'dev ALL=(ALL) NOPASSWD:ALL' > /etc/sudoers.d/devMove the repository, the agent's credentials, and its memory into that user's home. Own every file:
chown -R dev:dev /home/devRoot can still run the agent if you export IS_SANDBOX=1, which tells Claude Code the sandbox is the isolation boundary. Prefer the non-root user. It does not depend on that escape hatch.
Fix the login shell first
Terminals open a login shell. A login shell reads ~/.bash_profile or ~/.profile. It does not read ~/.bashrc. Anything you put only in .bashrc is invisible.
printf '[ -f ~/.bashrc ] && . ~/.bashrc\n' > ~/.bash_profileThis one file is why an alias, an environment variable, or a shell enhancement can look broken while the file that defines it is correct.
Shell quality of life
Install ble.sh for fish-style autosuggestions and syntax highlighting in bash. Then set a large, shared history:
export HISTSIZE=100000 HISTFILESIZE=200000 HISTCONTROL=ignoreboth:erasedups
shopt -s histappend
bleopt complete_auto_complete=1
bleopt history_share=1history_share=1 matters with multiple terminals. Without it each terminal keeps its own history until it exits. A command run in one tab never appears in another tab's suggestions, which reads as broken autocomplete.
Tabs: terminals that survive
Open https://www.platinum.dev/tabs?id=<sandbox-id>. Each tab is a tmux session inside the sandbox, so terminals survive a browser close, a reload, and a device switch. Close a tab with ✕ to end that session.
Warning: set window-size manual per window, never globally. Global window-size manual segfaults tmux 3.3a when a second session is created, which kills every terminal at once.
tmux set -gu window-size # clear a global setting
tmux set -w window-size manual # scope it to the windowGive the agent its own key
Mint a sandbox-scoped key. It can drive this sandbox and its children, and nothing else in your organization.
pt key create --name agent --scope "sandbox:sbx_01K…"Inject it into the sandbox. A sandbox never mints its own key.
cat > /etc/pt-agent.env <<'EOS'
export PLATINUM_API_KEY="pt_live_…"
export PT_API_URL="https://api.platinum.dev"
EOSSource that file from every shell, so the agent inherits it whichever user it runs as.
Spawn children for heavy work
This is the payoff. The agent runs a build, a migration, or a load test in a child sandbox instead of the box it lives on.
pt sandbox create -t debian-dev --cpu 2 --ram 6144 --disk 20 --no-wait
pt sandbox list --parent <this-sandbox-id>
pt sandbox exec <child-id> -- bash -lc 'cargo check'
pt sandbox rm <child-id> --yesA scoped key needs no --parent. New sandboxes are parented to the scoped sandbox automatically, so the agent cannot create a sandbox it later cannot see.
Children run concurrently. Four created in parallel took 5 seconds of wall clock, against roughly 20 seconds one after another.
Children are siblings, not a tree. A child holding the same key creates siblings of itself, because the scope covers one level. Use this for parallel work pools, not for recursive agent hierarchies.
Deleting children safely
Warning: pt sandbox children <id> -o json prints each child's parentId. A loop that greps sandbox ids out of that output matches the parent too, and deletes the sandbox you are working on.
Take ids from the id field only:
pt sandbox children <id> -o json \
| python3 -c 'import sys,json;[print(x["id"]) for x in json.load(sys.stdin)]'A sandbox-scoped key is refused with 403 cannot_delete_scope_root if it tries to delete the sandbox it is scoped to. A full-access key is not. Parse ids carefully whichever key you hold.
Delete children before the parent. A parent with live children returns 409 and names them.
Back up before you experiment
Sandboxes back up to object storage. Check that a backup exists before risky work:
pt sandbox get <id> -o json | grep -i backupA deleted sandbox is recoverable from its backup while the row survives. You lose everything written after the last backup, so take one before a migration or a large refactor.