# How to run Docker Compose integration tests in a cloud sandbox Upload the repo, run sudo enable-docker, then docker compose up --wait and docker compose run your test service; copy the reports out. **On Runtime, a Compose stack gets a whole microVM, not a shared Docker host.** Every sandbox is a Firecracker machine with its own kernel and its own Docker daemon, so parallel CI jobs never collide on ports, volumes or container names, and root inside one cannot touch another. A stack that idles between requests pays mostly for memory: $0.03125 an hour for 2 vCPU and 4 GiB at rest and $0.08 at full load ([pricing](/docs/pricing)). On 25 September 2026, Ubuntu 24.04's updates carried Docker 29.1.3 and Compose 2.40.3, the archive `enable-docker` installs from. ## Run the suite The pattern: start the services the tests depend on, wait until their health checks pass, run the test container once, keep its exit code. ```ts check import { Sandbox } from "withruntime"; await using sbx = await Sandbox.create({ diskMiB: 10_240, timeoutSeconds: 3600 }); const long = { timeoutMs: 1_800_000, cwd: "/workspace/app" } as const; await sbx.files.upload("./", "/workspace/app"); await sbx.exec("sudo enable-docker", { check: true, timeoutMs: 600_000 }); await sbx.exec("docker compose up -d --build --wait --wait-timeout 300 db cache api", { ...long, check: true, }); const tests = await sbx.exec("docker compose run --rm tests", { ...long, onStdout: (text) => process.stdout.write(text), }); if (tests.exitCode !== 0) { const logs = await sbx.exec("docker compose logs --no-color --tail 200 api db", long); console.error(logs.stdout); } await sbx.files.download("/workspace/app/reports", "./reports"); process.exitCode = tests.exitCode ?? 1; ``` ```python check import sys from withruntime import Sandbox with Sandbox.create(disk_mib=10_240, timeout_seconds=3600) as sbx: sbx.files.upload(".", "/workspace/app") sbx.exec("sudo enable-docker", check=True, timeout_ms=600_000) sbx.exec("docker compose up -d --build --wait --wait-timeout 300 db cache api", cwd="/workspace/app", check=True, timeout_ms=1_800_000) tests = sbx.exec("docker compose run --rm tests", cwd="/workspace/app", timeout_ms=1_800_000, on_stdout=sys.stdout.write) if tests.exit_code != 0: print(sbx.exec("docker compose logs --no-color --tail 200 api db", cwd="/workspace/app").stdout, file=sys.stderr) sbx.files.download("/workspace/app/reports", "reports") sys.exit(tests.exit_code or 0) ``` For the reports to reach the sandbox's disk, the `tests` service mounts the folder, for example `volumes: ["./reports:/app/reports"]` in the Compose file. Output streams while the suite runs, and a failed run brings the last 200 log lines of the services it talked to. ## The Compose flags that matter in CI From Docker's reference for `docker compose up`, read 25 September 2026: | Flag | What it does | | --------------------------- | --------------------------------------------------------------------- | | `--wait` | Waits for services to be running or healthy; implies detached mode | | `--wait-timeout 300` | Gives up after that many seconds | | `--build` | Builds images before starting containers | | `--exit-code-from tests` | Returns that service's exit code; implies `--abort-on-container-exit` | | `--abort-on-container-exit` | Stops every container when one stops; cannot be combined with `-d` | `--wait` is only as good as the health checks: a service with no `healthcheck` counts as ready once it is running. `up --exit-code-from tests` is the one-command alternative to `run --rm tests` when the test service is part of the default stack. ## Shard the suite across copies of a warm stack Building images and seeding a database is the slow part. Do it once, then [fork](/glossary/sandbox-fork) the sandbox: each copy starts with the same files, memory and running processes, containers included, and runs a different slice of the tests. ```ts check import { Sandbox } from "withruntime"; await using base = await Sandbox.create({ diskMiB: 10_240, timeoutSeconds: 3600 }); const app = { cwd: "/workspace/app", check: true, timeoutMs: 1_800_000 } as const; await base.files.upload("./", "/workspace/app"); await base.exec("sudo enable-docker", { check: true, timeoutMs: 600_000 }); await base.exec("docker compose up -d --build --wait db cache api", app); await base.exec("docker compose run --rm tests npm run seed", app); const shards = await base.fork({ count: 4 }); const results = await Promise.all( shards.map((copy, i) => copy.exec(`docker compose run --rm tests npx jest --shard=${i + 1}/4`, { cwd: "/workspace/app", timeoutMs: 1_800_000, }), ), ); await Promise.all(shards.map((copy) => copy.stop())); process.exitCode = results.some((r) => r.exitCode !== 0) ? 1 : 0; ``` A fork pauses the source for about a second on a fresh sandbox, longer the more memory it holds, and each copy is billed as its own sandbox. A trial account runs eight sandboxes at once and a paid one starts at 100. ## Look inside a stack from your machine Leave the sandbox running and forward the ports Compose published, several in one command: ```bash no-run runtime sandbox port-forward 8080 15432:5432 6379 ``` Now `localhost:8080` is the API, `localhost:15432` its Postgres, and `localhost:6379` its Redis, all over Runtime's authenticated API with nothing opened to the internet ([forward ports](/docs/editors#forward-ports)). To show the app to someone else, share the port as a private HTTPS [preview](/docs/javascript#share-a-port) instead. If you step away mid-debug, `runtime sandbox pause ` keeps the containers exactly as they are, memory included, and the next command wakes the sandbox ([pause and resume](/how-to/pause-and-resume-a-sandbox)). ## Sizing and limits - **Disk.** Images and volumes live on the sandbox's disk. The samples ask for 10 GiB, the free trial's largest; paid sandboxes can ask for more. - **Service-to-service calls.** Clients that honour `HTTP_PROXY` need the other services' names in `NO_PROXY`; the [Docker how-to](/how-to/run-docker-in-a-sandbox) shows the two lines to add. - **Outbound ports.** Pulls go through `mirror.gcr.io` first and reach the web on 443. A trial sandbox reaches ports 80 and 443 only; a paid one reaches any port, such as a staging database outside the stack. - **Startup.** Put Docker in a [custom image](/docs/images) with `enable-docker --no-start` and every sandbox skips the install. For agents that build and run multi-service apps, see [preview agent-built apps](/use-cases/preview-agent-built-apps) and [a coding agent sandbox](/use-cases/coding-agent-sandbox). ## Sources - Docker, `docker compose up` reference, https://docs.docker.com/reference/cli/docker/compose/up/, read 25 September 2026 - Launchpad, published sources for docker.io-app and docker-compose-v2 in Ubuntu 24.04, https://launchpad.net/ubuntu/noble, read 25 September 2026 Facts on this page were checked on 25 September 2026.