Docker in sandboxes
A sandbox can run a stock Docker Engine - docker run, docker build, Compose, and port publishing on the default bridge network, with Docker's own firewall rules intact. Every sandbox is its own virtual machine with its own kernel, so containers you start inside it are isolated from other sandboxes the same way everything else in the sandbox is.
Use it for CI jobs, for agents that need to build and run a project's own Dockerfile, or for anything that ships as a Compose file.
1. Build a Docker template (once)
Install Docker Engine from Docker's official Debian repository into a custom template on top of debian-12. The template's start_command starts the Docker daemon on every boot, so sandboxes come up ready with no setup calls. The recipe needs open network while building because Docker's package repository is not on the default allowlist.
curl -X POST https://api.orkestr.eu/v1/templates \
-H "Authorization: Bearer $ORKESTR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "docker",
"base_template": "debian-12",
"network": "open",
"recipe": [
"apt-get update && apt-get install -y ca-certificates curl iptables e2fsprogs",
"install -m 0755 -d /etc/apt/keyrings && curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc && echo \"deb [arch=amd64 signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/debian bookworm stable\" > /etc/apt/sources.list.d/docker.list",
"apt-get update && apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin",
"curl -fsSL https://orkestr.eu/scripts/sandbox-docker-boot.sh -o /usr/local/bin/orkestr-docker-boot && curl -fsSL https://orkestr.eu/scripts/sandbox-docker-wait.sh -o /usr/local/bin/docker-wait && chmod 0755 /usr/local/bin/orkestr-docker-boot /usr/local/bin/docker-wait"
],
"start_command": "/usr/local/bin/orkestr-docker-boot"
}'
# => { "id": "tmpl_01J...", "status": "building", ... } (about a minute)The boot script is short and readable: sandbox-docker-boot.sh. You can paste it into a recipe step instead of downloading it.
2. Run containers
from orkestr import Sandbox
# "docker" resolves to the template's default tag. Docker starts on boot.
with Sandbox.create(template="docker", size="medium", network="open") as sbx:
sbx.exec("docker-wait") # blocks until the daemon answers
print(sbx.exec("docker run --rm node:26-alpine node -e 'console.log(1+1)'").stdout) # 2
# Publish a port the normal way, then reach it from the internet:
sbx.exec("docker run -d -p 8080:80 nginx:alpine")
print(sbx.preview_url(8080))docker-wait returns as soon as the daemon is up, usually within a second or two of boot. Ports you publish with -p are reachable inside the sandbox on localhost, and from anywhere through the sandbox's preview URL for that port.
Where Docker keeps its data
Docker needs an ext4 filesystem for its images and containers. The boot script picks one for you:
- With a volume attached, Docker's data goes on the volume (
/persist/docker). It is disk-backed, does not use the sandbox's memory, and pulled images plus build cache survive into the next sandbox that attaches the volume. This is the recommended setup for real builds, and it works even on asmallsandbox. Size the volume for your images. - Without a volume, Docker gets an in-memory disk. Only the blocks Docker actually writes use memory, but images still count against the sandbox's RAM, so use
mediumorlarge.
from orkestr import Sandbox
# Docker's data lives on the volume: disk-backed, and pulled images and
# build cache are still there the next time a sandbox attaches it.
sbx = Sandbox.create(
template="docker",
volume="docker-cache",
network="restricted",
allow_domains=["docker.io", "docker.com"],
)
sbx.exec("docker-wait && docker build -t app . && docker run --rm app")Networking
open- pulls from any registry.restricted- add the registries you pull from toallow_domains. For Docker Hub that isdocker.ioanddocker.com(entries cover their subdomains; Docker Hub serves image layers from adocker.comCDN host). The Docker daemon uses the sandbox's egress proxy automatically.off- no pulls, but images already cached on an attached volume run fine.
DOCKER_INSECURE_NO_IPTABLES_RAW. Rules inside the sandbox never widen its egress policy: that is enforced outside the sandbox.Options
Set these as environment variables when you create the sandbox:
ORKESTR_DOCKER_ARGS- extradockerdflags.ORKESTR_DOCKER_DISK_MB- size cap of the in-memory data disk when no volume is attached (default: what the sandbox's memory allows).
# Docker 29's nftables firewall backend instead of iptables:
Sandbox.create(template="docker", env={"ORKESTR_DOCKER_ARGS": "--firewall-backend=nftables"})
# Cap the in-memory data disk (MB) when no volume is attached:
Sandbox.create(template="docker", size="large", env={"ORKESTR_DOCKER_DISK_MB": "4096"})Tested with
- Docker Engine 29 from Docker's official Debian repository, on
debian-12 - Debian's default
iptables-nft, and Docker 29's nftables firewall backend docker run,docker build(BuildKit), Compose,-pport publishing- Pause and resume: the daemon and running containers are restored with the sandbox
Not supported: Swarm mode, and running virtual machines inside the sandbox (no nested virtualization).