On Runtime, Docker runs inside a microVM of its own. Every sandbox is a
Firecracker microVM with its own Linux kernel. The Docker daemon, your
containers and docker build all run inside that machine. 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. With both CPUs busy, it
costs $0.08 an hour (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 that, docker works without sudo. Installs
take longer than the 60-second default for a command, so set timeoutMs for
them.
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. A preview shares
it, and runtime sandboxes port-forward <id> 8080 brings it to your own
machine. A preview is private by default. A request needs its token, sent in
the x-runtime-preview-token header
(share a port).
Start every sandbox with Docker ready
Build a custom image whose recipe installs Docker. Every sandbox made from it
skips the install. With --no-start, the daemon starts 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 by its size. An account without credit can store its first three images free (custom images). An image can also come from your own Dockerfile. That image 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 | No proxy settings needed: their connections out go through the sandbox's relay |
| 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 sandboxes port-forward |
In a measurement on 24 September 2026, the default 4 GiB sandbox had about 2.5 GiB free. That is why the samples ask for 8 GiB or more (disk, CPU and memory).
Compose services calling each other. Services, and containers on one
Docker network, reach each other by name, as anywhere. Containers get no proxy
settings, so nothing needs to go in NO_PROXY.
Where 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. It is discarded with the sandbox.
- A coding agent can run a repository's own
docker composesetup unchanged.
To learn why a microVM, and not a container, is the boundary, see microVM vs container and Firecracker vs Docker.
Start
Terminalnpx withruntime sandboxes run --timeout 600 -- bash -c 'sudo enable-docker && docker run --rm hello-world'A new account gets 100 free hours of a 1 GB machine in its first 30 days, with no card. The first run prints a link to approve in your browser. For full details, see Docker in the sandbox environment.
Facts on this page were checked on 26 October 2026.