Skip to content
Runtime

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). 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.

TypeScriptimport { 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;
Pythonimport sysfrom withruntime import Sandboxwith 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 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.

TypeScriptimport { 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:

Terminalruntime sandboxes port-forward <id> 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). To show the app to someone else, share the port as a private HTTPS preview instead.

If you step away mid-debug, run runtime sandboxes pause <id>. It keeps the containers exactly as they are, memory included. The next command wakes the sandbox (pause and resume).

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 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 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 and a coding agent sandbox.

Sources

Facts on this page were checked on 25 October 2026.

Related.

100 free hoursin your first 30 days.

of a 1 GB machine

  • No credit card
  • Four sandboxes at once, 2 vCPU and 4 GiB each
  • Then prepaid credit from $10, no plan fee
Start free, no card
Start free, no card