Skip to content
Guides · Benchmarks

Docker Image Pull Time in a Sandbox, Measured

Updated October 27, 2026 .md

Short answer: Measured on 2026-10-27 in eu-central, 3 runs per size. A cold docker pull of node:22 (408 MB compressed) took a median 15.7 to 16.0 seconds, python:3.12-slim 3.7 to 3.9 s and alpine:3.20 about 1.6 s. Sandbox size made no difference. An image already on disk, also after a resume, starts in 0.2 s.

Conditions#

Date and time 2026-10-27. small 11:46 to 11:47 UTC, default 11:59 to 12:00 UTC, large 12:00 to 12:02 UTC, the extra tests below 12:03 to 12:04 UTC
Region eu-central
Sizes small (2 vCPU, 4 GB), default (4 vCPU, 8 GB), large (8 vCPU, 16 GB)
Docker 29.9.0, overlay2 storage driver, no registry mirror configured, BuildKit on (from docker info and /etc/docker/daemon.json)
Registry Docker Hub, anonymous pulls of official images, linux/amd64
Runs One fresh sandbox per size, 3 runs of each image, with docker system prune -af before every pull so each one started from an empty image store. Each sandbox was deleted after its runs
Neighbors Other sandboxes on the account were running at the same time, so the host and its network may not have been idle

These are measurements of Sandboxer only. We didn't test any other service. Results vary by host, by what else is using the network, and by Docker Hub itself over time.

The images#

A fresh sandbox has Docker running and no images (docker images was empty on all three sizes). We pulled three official images of very different sizes. Compressed size is the sum of the linux/amd64 layer sizes in the registry manifest; on-disk size is what docker image inspect reports after the pull.

Image Layers Compressed On disk
alpine:3.20 1 3.6 MB 8 MB
python:3.12-slim 4 43.5 MB 120 MB
node:22 8 408.4 MB 1,131 MB

Results#

Wall-clock seconds for docker pull -q <image> from an empty image store. Median of 3 runs, with the quickest and slowest run.

Image small default large
alpine:3.20 1.59 (1.59 to 1.61) 1.60 (1.58 to 2.29) 1.64 (1.52 to 1.90)
python:3.12-slim 3.67 (3.51 to 3.73) 3.85 (3.43 to 4.02) 3.72 (3.46 to 3.92)
node:22 15.65 (15.42 to 16.11) 15.95 (15.51 to 16.07) 15.67 (15.58 to 15.79)

With the image already present, in a small sandbox:

Step Run 1 Run 2 Run 3
docker pull node:22 (image already there) 0.89 s 0.87 s 0.86 s
docker run --rm node:22 node -v 0.18 s 0.18 s 0.20 s
The same docker run, after sbx stop --wait and sbx resume 0.21 s 0.18 s 0.17 s

How to read the numbers#

  • Size doesn't buy a faster pull. The three sizes landed within 2% of each other on node:22, well inside the spread between runs. Pick the size for what the containers do once they run.
  • Small images are mostly round trips. alpine:3.20 is 3.6 MB, yet its pull took 1.6 seconds: token, manifest index, manifest, config and one layer, each a separate request to Docker Hub. At that rate, ten small images pulled one after another would take about 16 seconds, mostly waiting.
  • Large images are mostly unpacking. node:22 came in at about 26 MB/s of compressed data end to end. Downloading its biggest layer on its own was more than three times faster than that (next section), so most of the 15.7 seconds goes into decompressing and writing 1.1 GB of files.
  • A present image is free. A docker pull of an image you already have still checks the registry (0.9 s) but downloads nothing. Skip the pull and docker run starts a container in 0.2 s.
  • Images survive a stop. After sbx stop --wait and sbx resume (memory restored), docker images still listed node:22 and the container started in the same 0.2 s. A stop snapshot keeps the whole disk, Docker images included.

Where the time goes in a large pull#

To split network from unpacking, we downloaded the largest node:22 layer (211.8 MB compressed) with curl straight from Docker Hub's blob endpoint, then timed gunzip on it, 3 times on a fresh small sandbox:

largest layer: 211.8 MB compressed
run=1 download=2.22 s (95 MB/s) gunzip=2.38 s
run=2 download=2.37 s (89 MB/s) gunzip=2.30 s
run=3 download=2.43 s (87 MB/s) gunzip=2.20 s

Scale that to the whole image and the download is roughly 4.5 seconds and the decompression roughly 4.5 seconds. The remaining 6 seconds or so is most likely Docker writing 1.1 GB of files into its layer store and registering the image; we didn't time that step on its own. gunzip works on one core, which is one reason more vCPUs didn't help.

We also pulled all three images at once (three docker pull commands in the background, then wait) on the same sandbox. The medians of the three single pulls add up to 20.9 seconds; the parallel pull took 16.33, 21.69 and 19.80 seconds, a median of 19.80. Parallel pulls saved about a second, and run to run varied more.

Docker Hub's pull limit is shared per /64#

A sandbox reaches Docker Hub over IPv6 first: registry-1.docker.io resolved to IPv6 addresses before IPv4. Docker Hub's usage docs (read 2026-10-27) say anonymous pull limits apply per IPv4 address or per IPv6 /64 subnet. Docker documents a check for your remaining pulls; we ran it in a fresh sandbox before pulling anything:

TOKEN=$(curl -s "https://auth.docker.io/token?service=registry.docker.io&scope=repository:ratelimitpreview/test:pull" | jq -r .token)
curl -s --head -H "Authorization: Bearer $TOKEN" \
  https://registry-1.docker.io/v2/ratelimitpreview/test/manifests/latest | grep -i ratelimit
docker-ratelimit-source: 2a01:4f9:…::
ratelimit-limit: 100;w=3600
ratelimit-remaining: 93;w=3600

The source is a /64, not the sandbox's own address, and 7 pulls had already been counted before this sandbox pulled anything, so other sandboxes on the same host share that budget. Over IPv4 (curl -4), the source was the host's shared IPv4 address, so that limit is shared too. If a CI job or an agent pulls many images, sign in with your own Docker account inside the sandbox (docker login) so pulls count against your account instead, or keep the images on disk so you don't pull at all.

Avoid the pull: keep images on disk#

The quickest pull is the one you skip. Two ways that the numbers above support:

  • Stop instead of delete. Images, volumes and the build cache stay on the sandbox's disk through sbx stop and sbx resume.
  • Start new sandboxes from a named snapshot that already has your images. Pull once, sbx snapshot <id> <name>, then sbx new --from <name>. The walkthrough is in Docker and nested KVM inside an agent sandbox, and what a snapshot keeps is in the snapshots docs.

The exact commands#

This script ran inside each sandbox, once per run (bash bench.sh 1, 2, 3):

#!/bin/bash
# usage: bench.sh <run-number>
for img in alpine:3.20 python:3.12-slim node:22; do
  docker system prune -af >/dev/null 2>&1
  t0=$(date +%s.%N)
  docker pull -q "$img" >/dev/null 2>/tmp/pull.err; rc=$?
  t1=$(date +%s.%N)
  size=$(docker image inspect -f '{{.Size}}' "$img" 2>/dev/null)
  printf 'run=%s img=%s rc=%s secs=%.2f size_mb=%.0f at=%s\n' "$1" "$img" "$rc" \
    "$(python3 -c "print($t1-$t0)")" "$(python3 -c "print(${size:-0}/1e6)")" "$(date -u +%TZ)"
  [ $rc -ne 0 ] && cat /tmp/pull.err
done

From our machine, per size:

id=$(sbx new --type small --name docker-pull-1 --no-env --ttl 3600 --json \
  | jq -r 'select(.event=="ready") | .id' | tail -1)
sbx exec "$id" "echo $(base64 -w0 bench.sh) | base64 -d > /home/user/bench.sh"
for r in 1 2 3; do sbx exec "$id" --timeout 600 "bash /home/user/bench.sh $r"; done
sbx delete "$id" --yes

The small sandbox printed:

run=1 img=alpine:3.20 rc=0 secs=1.61 size_mb=8 at=11:46:03Z
run=1 img=python:3.12-slim rc=0 secs=3.73 size_mb=120 at=11:46:07Z
run=1 img=node:22 rc=0 secs=16.11 size_mb=1131 at=11:46:23Z
run=2 img=alpine:3.20 rc=0 secs=1.59 size_mb=8 at=11:46:26Z
run=2 img=python:3.12-slim rc=0 secs=3.67 size_mb=120 at=11:46:30Z
run=2 img=node:22 rc=0 secs=15.42 size_mb=1131 at=11:46:45Z
run=3 img=alpine:3.20 rc=0 secs=1.59 size_mb=8 at=11:46:49Z
run=3 img=python:3.12-slim rc=0 secs=3.51 size_mb=120 at=11:46:52Z
run=3 img=node:22 rc=0 secs=15.65 size_mb=1131 at=11:47:08Z

Keep --timeout on sbx exec: it defaults to 30 seconds, and a cold pull of a bigger image than node:22 can pass that. For pulls that take minutes, run them with sbx exec --detach and poll. We got the compressed sizes from the registry manifest with docker buildx imagetools inspect --raw, picking the linux/amd64 entry.

What we left out#

  • xlarge. Not tested; it needs the $100/month plan or higher.
  • Other registries (GHCR, ECR, Quay) and private images. Their CDNs and auth differ.
  • docker build. A build adds base-image pulls plus your own steps; see CPU performance by sandbox size and disk throughput by sandbox size for the parts a build leans on.

FAQ#

Does a bigger sandbox pull images faster?#

Not in our runs. node:22 took a median 15.65 s on small, 15.95 s on default and 15.67 s on large. The time goes to network round trips and single-stream decompression, which extra vCPUs don't speed up.

Do pulled images survive stopping the sandbox?#

Yes. After sbx stop --wait and sbx resume, node:22 was still in docker images and docker run started in 0.2 s with no pull. Images are on the disk, and a stop snapshot keeps the disk.

Is there a registry mirror or pull-through cache?#

Not by default. docker info showed no registry mirrors and /etc/docker/daemon.json set only BuildKit, the log driver and the storage driver. Every cold pull went to Docker Hub.

Will I hit Docker Hub's rate limit?#

You can if you pull a lot anonymously. The limit was counted per IPv6 /64 shared with other sandboxes on the same host, and 7 of 100 pulls were already used when we checked. Run docker login with your own Docker account or keep images on disk.

Will my numbers match these?#

Not exactly. Our three runs of one image differed by up to 45% for alpine:3.20 and 5% for node:22. Results vary by host, by network load and by Docker Hub over time.

More guides

Top AI sandbox platforms for code execution in 2026, ranked

For running AI-written code we rank Sandboxer, Freestyle and E2B first: each gives code its own VM with an API or SDK and can pause with memory. Pick Daytona for Windows or GPUs, Modal or Northflank for GPUs at scale, E2B or Northflank to run in your own cloud, and Vercel Sandbox if your app lives on Vercel.

Read the guide
Best sandboxes for running coding agents (Claude Code, Codex, Gemini CLI) in 2026, ranked

For coding agents we rank Sandboxer first: a full Ubuntu VM with Claude Code and Codex preinstalled, Docker, and stop-and-resume that keeps the agent's session in memory. Freestyle and Amp Orbs also keep memory across pauses. Pick E2B for prebuilt agent templates on open source, Docker Sandboxes for free local runs, and Codespaces if you live in GitHub.

Read the guide
Best Daytona sandbox alternatives in 2026, ranked

The best Daytona alternatives depend on why you're switching. For a full Linux microVM on every sandbox with memory-saving stop and Docker, we rank Sandboxer first. E2B suits open source and self-hosting, Modal massive short-job fleets, Northflank bring-your-own-cloud with GPUs, and Vercel Sandbox apps on Vercel.

Read the guide
Best E2B alternatives in 2026, ranked

If you're leaving E2B for longer sessions or a fuller machine, we rank Sandboxer first, then Freestyle: both give each sandbox a full Linux VM that keeps memory across pauses, with Docker and no short session cap. Pick Daytona for Windows or GPUs, Northflank for bring-your-own-cloud, and stay on E2B if you need open source.

Read the guide