feat(docker): allow snapshotting a Paused sandbox on the Docker runtime
Why do you need it?
Snapshotting a Paused sandbox is always refused, on every runtime:
POST /v1/sandboxes/{id}/snapshots (sandbox state: Paused)
→ 409 {"code":"SNAPSHOT::INVALID_SOURCE_STATE","message":"Snapshot can only be created from a Running sandbox."}The check is PersistedSnapshotService._ensure_source_sandbox_running
(snapshot_service.py). It
applies to every runtime and is asserted by a test. On Kubernetes that makes sense: pause is itself
snapshot-based, so a Paused sandbox has no live container left to commit.
On the Docker runtime, though, pause is an in-place freeze, and the Docker snapshot path is just
container.commit(...)
(docker/snapshot_runtime.py).
docker commit works fine on a paused container: by default Docker pauses a container itself for the
duration of a commit. So on Docker the refusal is a policy choice, not a technical limit.
Repro (Docker runtime, gVisor runsc as the secure runtime):
- Create a sandbox and write
/root/paused-proof.txtin it. POST /v1/sandboxes/{id}/pause. OpenSandbox reportsPaused, and Docker reportspaused=true.POST /v1/sandboxes/{id}/snapshotsreturns 409SNAPSHOT::INVALID_SOURCE_STATE.docker commitof the same paused container succeeds in 8.6s. The container stayspaused=true, anddocker run <image> cat /root/paused-proof.txtprintswritten-before-pause.- After
resume, the same snapshot request returns 202.
Environment:
opensandbox/server:latest(image from 2026-07-12; the check is unchanged onmainatbdfcd01)execdv1.0.19- Docker 29.4.2,
overlayfs - runsc
release-20260817.0with--overlay2=none
Why it matters: we build a swarm control plane on OpenSandbox (alineo), where agents fork children from their own live sandbox via this snapshot API.
- Forks under a paused parent: an operator pausing a subtree is common, and every fork that lands under a paused parent fails. We currently hold the fork until the parent resumes, which unfreezes it just to copy its filesystem.
- Consistent checkpoints: the natural way to checkpoint a group of sandboxes consistently is
pause-all, snapshot-each, resume-all. That's impossible while snapshots require
Running.
How could it be?
Make the source-state check runtime-aware, e.g. a runtime capability such as
supports_snapshot_from_paused():
- Docker: accept
RunningandPaused.container.commitneeds no change; committing an already-paused container leaves it paused. - Kubernetes: keep refusing
Pausedas today.
Transitional states (Pausing, Resuming) can keep returning 409.
Other related information
- #1790 (fast-sandbox snapshot/pause/resume) and #1273 (snapshot-to-replacement design) touch the same area.
Source: opensandbox-group/OpenSandbox