# 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. Parallel CI jobs therefore never collide on ports, volumes or container names, and root inside one sandbox 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 an hour at full load ([pricing](/docs/pricing)). On 25 October 2026, Ubuntu 24.04's updates carried Docker 29.1.3 and Compose 2.40.3. The archive that `enable-docker` installs from carries the same versions. ## Run the suite The pattern is as follows. Start the services the tests depend on. Wait until their health checks pass. Run the test container once, and 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 must mount the folder. For example, add `volumes: ["./reports:/app/reports"]` to the Compose file. Output streams while the suite runs. If a run fails, the sample also prints 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 October 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 that once, then [fork](/glossary/sandbox-fork) the sandbox. Each copy starts with the same files, memory and running processes, including its containers. Each copy then 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 while it captures the state. The source then runs again. Each copy is billed as its own sandbox. An account without credit runs four sandboxes at once. A paid account runs 100. ## Look inside a stack from your machine Leave the sandbox running, and forward the ports Compose published. You can forward several in one command: ```bash no-run runtime sandboxes port-forward 8080 15432:5432 6379 ``` Now `localhost:8080` reaches the API, `localhost:15432` reaches its Postgres, and `localhost:6379` reaches its Redis. The traffic goes through Runtime's authenticated API. Nothing is 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, run `runtime sandboxes pause `. It keeps the containers exactly as they are, memory included. 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 largest size without credit. Paid sandboxes can ask for more. - **Service-to-service calls.** Clients that honor `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 then reach the web on port 443. A sandbox without credit reaches ports 80 and 443 only. A paid sandbox reaches any port, such as a staging database outside the stack. - **Startup.** Put Docker in a [custom image](/docs/images) built with `enable-docker --no-start`. Every sandbox then 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 October 2026 - Launchpad, published sources for docker.io-app and docker-compose-v2 in Ubuntu 24.04, https://launchpad.net/ubuntu/noble, read 25 October 2026 Facts on this page were checked on 25 October 2026.