How to run Docker inside a sandbox
Run sudo enable-docker once in the sandbox; it installs Docker Engine, Buildx and Compose, and docker then works as the sandbox user.
On Runtime, Docker runs inside a microVM of its own. Every sandbox is a
Firecracker microVM with its own Linux kernel, so the Docker daemon, your
containers and docker build all run inside that machine, and root inside it
still cannot change its network, CPU, memory or cost. docker run,
docker build and docker compose work as they do on any Linux machine. A
2 vCPU, 4 GiB sandbox costs $0.03125 an hour while its containers wait and
$0.08 an hour with both CPUs busy (pricing).
Run a container
TypeScriptimport { Sandbox } from "withruntime";await using sbx = await Sandbox.create({ diskMiB: 8192, timeoutSeconds: 1800 });const slow = { check: true, timeoutMs: 600_000 } as const;await sbx.exec("sudo enable-docker", slow);const hello = await sbx.exec("docker run --rm hello-world", slow);console.log(hello.stdout);Pythonfrom withruntime import Sandboxwith Sandbox.create(disk_mib=8192, timeout_seconds=1800) as sbx: sbx.exec("sudo enable-docker", check=True, timeout_ms=600_000) hello = sbx.exec("docker run --rm hello-world", check=True, timeout_ms=600_000) print(hello.stdout)Terminalsudo enable-docker # once per sandbox; the first docker command runs it toodocker run --rm hello-worlddocker compose up -denable-docker installs Docker Engine, Buildx and Compose from Ubuntu's
archive and starts them. After it, docker works without sudo. Installs take
longer than the 60-second default for a command, so give them timeoutMs.
Run a Compose project and open it in a browser
Upload the project, start it, and share the published port as a private HTTPS preview:
TypeScriptimport { Sandbox } from "withruntime";await using sbx = await Sandbox.create({ diskMiB: 16_384, timeoutSeconds: 3600 });const slow = { check: true, timeoutMs: 900_000 } as const;await sbx.files.upload("./app", "/workspace/app");await sbx.exec("sudo enable-docker", slow);await sbx.exec("docker compose up -d --build", { ...slow, cwd: "/workspace/app" });const preview = await sbx.previews.create(8080); // the port compose publishesconsole.log(preview.url, preview.token);Pythonfrom withruntime import Sandboxwith Sandbox.create(disk_mib=16_384, timeout_seconds=3600) as sbx: sbx.files.upload("./app", "/workspace/app") sbx.exec("sudo enable-docker", check=True, timeout_ms=900_000) sbx.exec("docker compose up -d --build", cwd="/workspace/app", check=True, timeout_ms=900_000) preview = sbx.previews.create(8080) # the port compose publishes print(preview["url"])A published port (-p 8080:80) listens inside the sandbox, so a preview shares
it and runtime sandbox port-forward <id> 8080 brings it to your own machine.
A preview is private by default: a request needs its token, sent as the
x-runtime-preview-token header (share a port).
Start every sandbox with Docker ready
Build a custom image whose recipe installs Docker, and every sandbox made from
it skips the install. --no-start leaves the daemon to start on the first
docker command:
TypeScriptimport { Runtime } from "withruntime";const runtime = new Runtime();await runtime.images.build({ name: "docker", recipe: { commands: ["enable-docker --no-start"] },});await using sbx = await runtime.sandboxes.create({ image: "docker", diskMiB: 8192 });await sbx.exec("docker run --rm hello-world", { check: true, timeoutMs: 300_000 });Pythonfrom withruntime import Runtimeruntime = Runtime()runtime.images.build(name="docker", recipe={"commands": ["enable-docker --no-start"]})with runtime.sandboxes.create(image="docker", disk_mib=8192) as sbx: sbx.exec("docker run --rm hello-world", check=True, timeout_ms=300_000)Building an image is free; a stored image is charged on its size, and the free trial stores your first three free (custom images). An image can also come from your own Dockerfile, which runs as a Runtime image without Docker in the sandbox at all.
Options and limits
| What | How it works |
|---|---|
| Install | sudo enable-docker, once per sandbox, or in an image with enable-docker --no-start |
| Daemon | Starts on the first docker command after a boot; a sandbox that runs no containers spends nothing on it |
| Disk | Images and containers live on the sandbox's disk. Ask for more than the default 4 GiB, such as diskMiB: 8192 |
| Pulls | Docker Hub images come through Google's public mirror, mirror.gcr.io, first |
| Internet from containers | Through the sandbox's proxy: each container and each docker build step gets HTTP_PROXY and HTTPS_PROXY set to http://172.17.0.1:10800 |
| Network rules | The sandbox's allow and deny lists apply to its containers too |
| Published ports | Listen inside the sandbox; share them with a preview or runtime sandbox port-forward |
The default 4 GiB sandbox had about 2.5 GiB free in a measurement on 24 September 2026, which is why the samples ask for 8 GiB or more (disk, CPU and memory).
Compose services calling each other. Services reach each other by name, as
anywhere. An HTTP client that honours HTTP_PROXY sends a call to another
service through the proxy unless that name is in NO_PROXY, so list the names
a service calls in its environment, under both spellings:
YAMLservices: web: build: . ports: ["8080:3000"] environment: NO_PROXY: localhost,api,db no_proxy: localhost,api,db api: build: ./api db: image: postgres:17Where this helps
- An agent that builds and runs a multi-service app can do it in a sandbox and show you the result through a preview (preview agent-built apps).
- Integration tests that need a real Postgres or Redis get their own, thrown away with the sandbox.
- A coding agent can run a repository's own
docker composesetup unchanged.
For why a microVM and not a container is the boundary, see microVM vs container and Firecracker vs Docker.
Start
Terminalnpx withruntime sandbox run --trial --timeout 600 -- bash -c 'sudo enable-docker && docker run --rm hello-world'New accounts get 50 free sandbox hours, no card. The first run prints a link to approve in your browser. Full details are in Docker in the sandbox environment.
Facts on this page were checked on 25 September 2026.