Guides

Parallel and background work

Guide

How do I run Docker Compose inside an AI agent's sandbox?

Ask ChatGPT Ask Claude

Give the agent a whole machine with its own Docker Engine instead of a share of yours. A Sanbox Docker Computer is a Firecracker microVM with Docker Engine, Buildx and Compose already set up: you allow the registries your images come from, copy the project in with sanbox cp, and run docker compose with sanbox exec. The common alternative, mounting your host's Docker socket into the agent's container, hands it control of your Docker daemon, and Docker's own docs say only trusted users should have that.

Run this with your agent

Read https://sanbox.cloud/kb/parallel-work/docker-compose-inside-an-agent-computer.md and follow it to run my Compose stack in a Docker Computer.
If the Sanbox CLI is missing, install it with `npm install -g @sanlabs/sanbox-cli@latest`
and sign in with `sanbox login`. Show me each command before you create, stop or delete a Computer.

What's in a Docker Computer?

  • Docker Engine, Buildx and Compose inside the run's own microVM. It doesn't share Docker with anyone, and containers, builds and Compose stacks need no special privileges, because the run is the security boundary.
  • A small Debian bookworm-slim image with git, curl, jq, zip and unzip, openssh and zellij. There is no Node and no Python preinstalled.
  • No agent and no model. It takes no task and has no chat. Your coding agent, CI job or application drives it with sanbox exec, sanbox cp, the console terminal or SSH.
  • Defaults of 2 vCPUs, 8 GB of memory and an 8 GB disk.
  • The sanbox user runs commands and can use docker without sudo.

sanbox exec, sanbox cp and the docker-computer type need CLI 0.0.19 or later.

Which hosts does the network policy need?

A new Docker Computer can't reach the internet. Image pulls go through the run's egress proxy, so list every registry you pull from, on port 443:

  • Docker Hub: registry-1.docker.io, auth.docker.io, and both layer CDNs, production.cloudfront.docker.com and production.cloudflare.docker.com
  • GitHub Container Registry: ghcr.io and pkg-containers.githubusercontent.com
  • Package mirrors your builds use, such as deb.debian.org, pypi.org or registry.npmjs.org, each need their own rule for the ports they use

Save this as registries.json for Docker Hub:

{
  "default_action": "deny",
  "rules": [
    { "destination": "registry-1.docker.io", "ports": [{ "from": 443, "to": 443 }] },
    { "destination": "auth.docker.io", "ports": [{ "from": 443, "to": 443 }] },
    { "destination": "production.cloudfront.docker.com", "ports": [{ "from": 443, "to": 443 }] },
    { "destination": "production.cloudflare.docker.com", "ports": [{ "from": 443, "to": 443 }] }
  ]
}

A pull blocked by the policy names the host it couldn't reach, so add hosts as errors show them. A template can hold at most 16 rules.

Steps

1. Create the template

sanbox templates create --name "Compose stack" --type docker-computer \
  --network-policy registries.json --json

2. Start a Docker Computer

sanbox run --template <template-id> --json
sanbox runs get <run-id> --json

Wait until the run is running.

3. Copy the project in

sanbox cp ./app <run-id>:/workspace

This creates /workspace/app. Uploads must finish within about five minutes, so send source code, not build output.

4. Start the stack and run the tests

sanbox exec <run-id> --cwd /workspace/app -- docker compose up -d
sanbox exec <run-id> --cwd /workspace/app -- docker compose exec -T api pytest

sanbox exec streams output and exits with the command's exit code, so your agent or CI job sees whether the tests passed.

5. Run long jobs detached

A dropped connection doesn't stop a command. For jobs longer than the default 3,600-second timeout, raise it (up to 86,400 seconds) and detach:

sanbox exec <run-id> --detach --timeout 86400 --request-key <key> --json -- ./long-job.sh
sanbox commands logs <run-id> <command-id> --follow

Reuse the same --request-key when you retry, so a lost response can't start the job twice. Each command keeps its newest 8 MiB of output, and the run forgets commands when it stops.

6. Copy results out

sanbox cp <run-id>:/workspace/app/reports .

This creates ./reports on your machine.

Why can't my services reach each other over HTTP?

Containers reach the network only through the run's proxy at http://169.254.77.1:3128, and Docker passes it to containers and builds automatically. HTTP calls between Compose services go to that proxy too, unless the service names are in NO_PROXY. Add them to each service that makes such calls:

services:
  api:
    build: .
    restart: unless-stopped
    environment:
      NO_PROXY: "worker,localhost,127.0.0.1"
  worker:
    build: ./worker
    restart: unless-stopped

Does the stack survive a stop?

Images, build caches and volumes live on the Computer's disk, so stop, resume, fork, snapshots and warm pools keep them.

  • Containers come back after a resume only with a restart policy, such as restart: unless-stopped. Compose's docs define it as restarting regardless of exit code, unless the service was stopped or removed.
  • Stop saves everything written before it, then gives containers up to 15 seconds to exit before they're killed.
  • Registry logins are held in memory for the current boot. Run docker login again after a resume. Logins aren't saved in snapshots.

The disk holds images, volumes and /workspace. Docker Computers default to 8 GB, which is also the most sanbox templates storage set <template-id> --gb <1-8> allows. Larger disks are available on request.

Can I use Testcontainers?

Testcontainers starts containers by talking to the Docker daemon, so a suite that uses it can run inside the Docker Computer, next to its Docker Engine. Three things to plan for:

  • Install your test runtime first. The image has no Node, Python or Java.
  • Allow Docker Hub. Testcontainers pulls its own helper images, including its Ryuk cleanup container (testcontainers/ryuk), as well as the images your tests ask for.
  • Proxy settings reach containers through the Docker client's configuration. Docker's docs describe that file as client configuration, so containers that a library creates directly through the Docker API may not get it. If such a container needs the internet, set HTTP_PROXY and HTTPS_PROXY to http://169.254.77.1:3128 on it yourself.

Snapshot it

Pull images and build once, then reuse the result. Stop the prepared Computer and capture it (this needs an organisation admin or owner):

sanbox runs stop <run-id> --wait --json
sanbox snapshots create --run <run-id> --name compose-ready
sanbox pools create --snapshot compose-ready --name compose-pool --size 2
sanbox run --snapshot compose-ready --json

The snapshot keeps images, containers and data. Containers kept by a snapshot or warm pool keep their network access, because the proxy address is the same in every run.

What this doesn't do

  • No agent inside. A Docker Computer is just a computer. Drive it from your own agent or pipeline.
  • No public ports. Services are reachable only through authenticated port connections, for HTTP and SSE. See how to preview a dev server.
  • No GPUs.

Sources