Agent Sandboxes and CI
Give each agent task or CI job its own microVM. Choose the lifecycle based on what must survive after the command finishes.
For a complete example with no existing project, follow the agent quickstart: it prepares a machine, checks four branches for isolation, saves a checkpoint, restores it, and cleans up.
Choose a lifecycle
| Mode | Behavior | Use it for |
|---|---|---|
| Ephemeral run | Creates a machine, runs one command, then deletes it | Untrusted scripts, CI jobs, one agent turn |
| Persistent machine | Keeps disk state across stop and start | Development agents and jobs that need later inspection |
| Pack | Prebuilds dependencies into a portable .smolmachine artifact | Repeated jobs on compatible hosts |
| Branch | Clones a running source machine with copy-on-write RAM and disk | Many short workers from one warm state |
Run an ephemeral job
Network access is off unless enabled — but the image pull runs inside the
guest, so a registry image needs --net even when the job itself needs no
egress. On a CI host that runs the same image repeatedly, add --oci-cache so
later runs start from the host copy instead of pulling again:
smolvm machine run --net --image alpine:3.20 -- \
sh -c "uname -a && echo isolated" For an existing Node project with a test script and dependencies already
available to the guest, mount the source tree read-only and give generated
output a separate writable directory (tests must not write into the source tree):
mkdir -p artifacts
smolvm machine run --net --image node:22-alpine \
--volume "$PWD:/workspace:ro" \
--volume "$PWD/artifacts:/artifacts" -- \
sh -c "cd /workspace && npm test > /artifacts/test.log" A mount deliberately exposes a host directory to guest code. Do not mount the repository writable unless the job must edit it.
Restrict egress
Enable only the destinations the job needs. This standalone example checks the npm registry; it does not assume a project or lockfile exists in the image. Pre-pull the image with networking first, then reuse the host OCI cache so the restricted job does not need additional registry/auth/CDN hosts:
smolvm machine run --net --oci-cache --image node:22-alpine -- node --version
smolvm machine run --net --oci-cache \
--allow-host registry.npmjs.org \
--image node:22-alpine -- \
npm ping --registry=https://registry.npmjs.org Hostname and CIDR allow lists reduce the network authority of compromised dependencies or prompt-injected agents. A first-class deny-list is not currently available; if a job needs broad internet access, apply host or fleet network controls as well.
Use a persistent machine for debugging
smolvm machine create --name failed-job --net --image ubuntu:24.04
smolvm machine start --name failed-job
smolvm machine exec --name failed-job -- sh -c 'echo "test failed" > /root/test.log; exit 1' If the job fails, inspect it before cleanup:
smolvm machine status --name failed-job
smolvm machine shell --name failed-job
smolvm machine stop --name failed-job
smolvm machine delete --name failed-job Stopping preserves disk state but loses RAM. To retain RAM and process state,
start the machine with --branchable before running the job and use machine checkpoint while it is still running; capture eligibility depends on
the host and attachments. See the checkpoint reference.
Prebuild repeated environments
For an existing project whose Smolfile installs dependencies and includes ci.sh, create a pack:
smolvm pack create -s Smolfile -o ci-worker
./ci-worker run -- ./ci.sh Packs avoid repeating image pulls and setup. They are cold artifacts and require a compatible host architecture.
For repeated jobs, prepare and start a persistent source machine as branchable:
smolvm machine create --name agent-source --net --image alpine
smolvm machine start --name agent-source --branchable
smolvm machine exec --name agent-source -- apk add git
smolvm machine branch --from agent-source --name agent-1
smolvm machine branch --from agent-source --name agent-2
smolvm machine exec --name agent-1 -- git --version
smolvm machine exec --name agent-2 -- git --version
smolvm machine delete --name agent-1 --force
smolvm machine delete --name agent-2 --force
smolvm machine delete --name agent-source --force Each branch gets copy-on-write RAM and disk; the source continues after a brief capture pause. Branches remain on the source’s host and architecture. Delete every worker when its task ends.
Handle secrets
Secret injection places plaintext in the guest:
export API_TOKEN
smolvm machine run --net --secret-env AGENT_TOKEN=API_TOKEN \
--image alpine:3.20 -- sh -c 'test -n "$AGENT_TOKEN" && echo "token available"' Guest code can read AGENT_TOKEN. Use injection only when the whole guest workload is trusted with the value.
SSH-agent forwarding keeps private key material on the host, but the guest can request signatures while the socket is connected:
smolvm machine run --ssh-agent --net --image alpine:3.20 -- \
sh -c "apk add -q openssh-client && ssh-add -l" There is no shipped general HTTP credential broker or cloud-native secrets store. Avoid passing production credentials to untrusted agent code. Prefer short-lived, least-privilege credentials and scope them to one job.
Cleanup on every path
For a previously created ci-job with /root/ci.sh installed, preserve the
workload’s exit code while still deleting the persistent machine:
set +e
smolvm machine exec --name ci-job -- sh /root/ci.sh
status=$?
set -e
smolvm machine delete --name ci-job --force
exit "$status" SDK callers should delete machines in finally blocks or use the Python context manager. Add a TTL for cloud jobs when the SDK or API supports it so process crashes do not leave machines running indefinitely.