Skip to content

Sandboxes

slidict-ai-team[bot] edited this page Oct 15, 2026 · 2 revisions

Sandboxes

An additive, optional part of wip.yml: named sandboxes are long-lived containers declared beside your app's stack, each with its own image and its own declared storage. They are what wip sandbox operates on.

Nothing here changes the stack. dependencies:, compose: and sync: keep working exactly as they did, and wip up / down / exec / sync never touch a sandbox. Declaring resources is also inert on its own: loading wip.yml — or running wip config — never creates, mounts or deletes anything. Only wip sandbox and wip volume do that.

version: 1
resource_namespace: example-project

volumes:
  - name: project
    persistent: true
    mount: /workspace
  - name: scratch
    persistent: false
    mount: /tmp/work

sandboxes:
  - name: first
    image: example/tool:1
    volumes: [project, scratch]
  - name: second
    image: example/tool:1
    volumes: [project]

resource_namespace

One explicit, stable identity for everything this wip.yml declares. It is required as soon as volumes: or sandboxes: has an entry, and it must match [a-z][a-z0-9_-]{0,62}.

Backend names are derived from it, so changing it designates different resources rather than renaming the existing ones. Keep the old value around if you still have resources to clean up.

volumes:

A sequence — not a mapping — so duplicate names are rejected instead of silently overwriting each other:

Key Required Contract
name yes Matches [a-z][a-z0-9_-]{0,62}, unique within volumes
persistent yes A real YAML boolean. There is no default
mount yes Canonical absolute Linux path: no control characters, no empty segments, no . or .., no trailing slash (except / itself), no commas
sync no A host directory this volume is mirrored from. See below

persistent has to be written out because the two values mean opposite things for your data, and defaulting would mean guessing in the destructive direction:

  • persistent: true — kept when a sandbox is destroyed, and reused by the next create.
  • persistent: false — ephemeral. Retained while any container, including a stopped one, references it. After recorded use and removal of the last reference, reconciliation removes it; unused volumes and unknown states are retained. After cleanup, the next create gets fresh storage.

Sandbox destruction runs reconciliation. Removing a container directly through the backend does not run it automatically.

sandboxes:

Also a sequence, also with unique names. This table covers the basic sandbox settings:

Key Required Contract
name yes Matches [a-z][a-z0-9_-]{0,62}, unique within sandboxes
image yes Nonempty, no control characters. Its default command must keep running — a container that exits at once is a failed creation, not a sandbox
volumes no Names of volumes declared above. Omitted means no mounts

There is no notion of a sandbox's role or purpose. wip infers nothing from a name, and agent/purpose/mode-style keys are rejected rather than ignored.

Sharing and isolation fall out of the references

Mount the same volume in two sandboxes and they share that storage. Only sandboxes referencing a volume are given that mount by wip. In the example below, "cannot see scratch" describes the mounts wip provides; host administrators and other backend clients may still access the storage:

sandboxes:
  - name: first
    image: example/tool:1
    volumes: [project, scratch]   # shares both
  - name: second
    image: example/tool:1
    volumes: [project]            # shares project, cannot see scratch

What is rejected at load

Catching these when the file is read is the point of the sequence-with-explicit-names shape:

  • a duplicate volume or sandbox name
  • a sandbox referencing a volume that isn't declared, or referencing the same one twice
  • two volumes mounted at the same path inside one sandbox (the same path in different sandboxes is fine)
  • unsupported keys in a volume or sandbox entry
  • a missing resource_namespace when resources are declared
  • a sync.mount overlapping the volume's own mount, or a sync without an image

A volume targeting / is accepted by configuration loading but cannot be mounted by wip sandbox create.

Nested mounts are not a conflict: a shallower path is ordered before a deeper one so the deeper mount isn't shadowed.

See Configuration Errors for how wip reports a rejected file.

Mirroring a host directory into a volume

A volume can declare the host directory it is mirrored from. Editing stays on the host; the sandboxes read the volume, which is native storage inside the VM rather than a bind mount shared over virtiofs — the same reason sync: exists for the stack.

volumes:
  - name: project
    persistent: true
    mount: /workspace
    sync:
      source: .            # relative to wip.yml's directory; default "."
      image: alpine-rsync  # required; the mirror runs rsync from this image
      exclude: [.git, node_modules]
Key Required Contract
source no Host directory, expanded against the wip.yml directory. Default .. It must exist
mount no Where the mirror sees the host tree, read-only. Default /host-src. Must not overlap the volume's mount
image yes The mirror's own image, which has to provide rsync
exclude no --exclude patterns
delete no Whether the mirror deletes what the host no longer has. Default true

image is required rather than borrowed from a sandbox: the mirror runs in a throwaway container of its own, and the images that mount the volume are not guaranteed to carry rsync.

Declaring it mirrors nothing. wip volume sync NAME runs one pass, over the volume that already exists — it creates no storage, so wip volume create (or wip sandbox create) comes first. The sandboxes that mount the volume keep running, and the host side is mounted read-only, so a mirror never writes back to your tree. Re-run it whenever the host tree has moved on; there is no watch mode for a declared volume.

delete: true is the default, and it empties what the source does not have. The mirror makes the volume match the host directory, so anything written inside the sandbox under that mount is removed on the next pass. Keep a sandbox's own work on a different volume.

Persistence

What survives which operation is the thing worth being precise about:

stop → create destroy → create
The container kept, then resumed removed, then a new one is run
Anything installed inside the container still there gone with the container layer
persistent: true volumes kept kept, and reused
persistent: false volumes kept fresh, once the last referencing container is gone

So the rule for a sandbox you rely on: put it in the image, or put it on a persistent volume. A package installed by hand into a running sandbox is gone the moment you destroy it.

Keeping a tool's own configuration

If a CLI inside the sandbox writes configuration or credentials, and that has to outlive the container, mount a persistent volume over the directory it writes to:

volumes:
  - name: tool-config
    persistent: true
    mount: /home/user/.config/example-tool

sandboxes:
  - name: first
    image: example/tool:1
    volumes: [tool-config]

Know where that directory actually is for the tool you are using — the path above is an example, not a convention wip imposes.

A persistent volume holding credentials is a secret store. It outlives the container by design, it is shared with every sandbox that mounts it, and wip neither encrypts it nor masks what is inside it. Restrict access to the machine it lives on, mount it only in the sandboxes that need it, keep it out of anything you publish, and prefer a short-lived credential where the tool supports one. (wip config masks secrets it can recognise in wip.yml — see Secret Masking — which says nothing about the contents of a volume.)

Declared storage on its own

Volumes can be driven directly, without going through a sandbox:

wip volume create NAME
wip volume status NAME
wip volume destroy NAME
wip volume reconcile NAME
wip volume sync NAME

wip sandbox create already ensures the volumes its sandbox references, so reach for these when you want to prepare or inspect storage independently of the container that uses it. The ephemeral cleanup pass is reconcile. volume destroy explicitly deletes unreferenced owned storage, including persistent volumes. volume sync mirrors a declared sync source into the volume.

Related

Clone this wiki locally