(project, name), backed by a btrfs subvolume stored separately from box roots. Persistent volumes survive box deletion: delete every box in a project and the volume stays, ready to attach to the next one. Box-owned volumes are the exception; Zomg creates them for zomg create --fork data clones and deletes them with the owning box after box deletion fully completes.
Reach for a data volume when you want state that outlives a box: a database directory, a model cache, a workspace you re-mount across experiments. To capture and restore a box’s own root filesystem, see snapshots and bases instead.
Mental model
- A volume is identified by
(project, name)and exists independently of any box. - You manage volumes with
zomg datasubcommands. - You make a volume’s contents visible inside a box by attaching it at a mount path.
- Normal data volumes have
persistentownership and outlive boxes. Generated box-owned volumes havebox_ownedownership, names likebox-<box-prefix>-<12 hex>, anddelete_with_owner=true. - A volume can be snapshotted locally (fast btrfs snapshots) and backed up remotely (to a
file://orgs://target). - All of this is disjoint from box-root snapshots (
zomg snapshot), which capture the box root, not data volumes.
Naming rules
Data volume names, snapshot names, and backup names use the same safe component format as boxes: 1-63 characters, lowercasea-z0-9-, starting and ending alphanumeric, and no --.
Project names follow the same DNS-label shape and also disallow --.
You can target a volume in another project with project:name syntax. The project part is validated as a project name and the name part as a data name. When you do not specify a project, resolution order is --project → ZOMG_PROJECT → git repo name → default. (ZOMG_PROFILE only selects the API endpoint and credentials, not the project.)
Create, list, delete
Create
create takes a required name positional. Flags:
--from <SOURCE>and--from-snapshot <SNAP>— clone from an existing volume’s snapshot (see below).-p, --project <P>-j, --json
OK (or JSON with --json). A plain create runs btrfs subvolume create; if a volume with that name already exists, it fails with data already exists.
Cloning from a snapshot
To create a new volume from an existing volume’s local snapshot, pass--from and --from-snapshot together:
Error: source data volume and new volume must be in the same project. The clone is a btrfs snapshot of the named snapshot, and the snapshot must already exist.
This is the only way to produce a new volume from existing data. Remote backups only restore in place onto the same volume (covered later).
List
-a, --all— walk every project (the default project plus every project that has a box). Adds aPROJECTcolumn.-q, --quiet— print onenameper line, orproject:namewith--all.-p, --project <P>-j, --json— print{"data":[...]}.
NAME SIZE CREATED; with --all it is NAME PROJECT SIZE CREATED. The JSON DataVolumeInfo records are {name, project, created_at, size_bytes, ownership} — there is no attachments field.
ownership.kind is persistent for normal user-managed volumes. Generated box-owned clone artifacts use ownership.kind = "box_owned" and delete_with_owner = true; they may appear in zomg data list --json, but Zomg attaches them to the owning clone and cleans them up with that box.
User
zomg data attach, zomg data detach, and zomg data delete reject box-owned volumes with data is box-owned. Delete the owning box to clean them up.Delete
delete takes a name positional and only the -p, --project flag.
On success it prints OK: (<project>:<name>) deleted; on failure it prints FAIL: (<project>:<name>) <msg> and exits 1.
Attach and detach
Attaching makes a volume’s contents appear inside a box at a mount path.attach takes two positionals — data (the volume) and box (the box) — and these flags:
--path <PATH>(required) — mount path inside the box; must be absolute and outside reserved roots.--read-only— mount read-only. The default is read-write.-p, --project <P>-j, --json
Error: data volume and box must be in the same project. On success it prints OK (or JSON).
A read-only attach becomes a read-only bind mount inside the runner. Mounts apply live only when the box is not stopped; detach removes the mount only when the box is running.
Detach
detach takes data and box positionals plus -p, --project and -j, --json. It enforces the same-project check, removes the mount, and is idempotent — detaching a volume that is not attached succeeds. It prints OK.
Mount-path restrictions
Mount paths are validated both client-side and server-side. A path is rejected if it is empty, not absolute, or normalizes to/, and if it equals or falls under any of these blocked prefixes:
| Condition | Error |
|---|---|
| Same volume already attached to that box | data already attached (409) |
| A different volume already mounted at that path on the box | mount path already in use (409) |
| Volume does not exist | data not found |
The zomg cp gotcha
zomg cp reads and writes through the host file API, operating on host paths under the box’s root subvolume. The server only redirects copies into its built-in system volumes, whose mount paths are /box/bin, /box/all, and /box/proj. Custom data-volume mount paths are not in that redirect list, so a cp targeting one writes to the box root rather than the volume.
To put files into a data volume, write to the mount path from inside the box (for example via zomg exec or zomg console), where the bind mount is live.
Local snapshots
Local snapshots are fast, read-only btrfs snapshots of a volume, kept on the same VM. Manage them withzomg data snapshot (alias zomg data snap).
Create a snapshot
data, and an optional name (auto-generated if omitted). Flags:
--name <NAME>— overrides the positionalnameif both are given.-m, --message <DESC>— stored as the snapshot description.-p, --project <P>-j, --json
v0, v1, and so on. The snapshot is a read-only btrfs snapshot. Creating a snapshot whose name is already taken fails with snapshot already exists; snapshotting a missing volume fails with data not found. On success it prints OK.
List snapshots
data, plus -p, --project and -j, --json. The table columns are NAME SIZE CREATED DESCRIPTION; JSON is {"snapshots":[...]} with records {name, created_at, size_bytes, description?}.
Restore a snapshot
data and snapshot, plus -p, --project only. Restore swaps the named snapshot into the same volume. Zomg refuses attached volumes before starting, then may clean up the replaced subvolume after the committed restore.
Contrast: box-root snapshots
Box-root snapshots are a separate feature:zomg snapshot (create/list/restore) captures a box’s root subvolume and stores it under the checkpoints directory. The two are completely disjoint — different commands, different storage. zomg data snapshot only touches data volumes; zomg snapshot only touches box roots. See snapshots and bases.
Remote backups
Remote backups send a volume’s data off the VM to a configured target usingbtrfs send. Manage them with zomg data backup. There is no delete subcommand.
The backup target
Backups and restores require a target configured by the deployment through theZOMG_BACKUP_TARGET environment variable on the API. If it is unset or empty, backups and restores fail with backup target is not configured.
Accepted schemes:
file://<absolute-path>— the path must be absolute, otherwisefile backup target must be absolute.gs://<bucket>[/<prefix>]— the bucket is required.
backup target must start with gs:// or file://. A gs:// target also requires GOOGLE_APPLICATION_CREDENTIALS to point at a service-account key JSON; if it is missing, backups fail with GOOGLE_APPLICATION_CREDENTIALS not set — GCS backup requires a service account key.
These values are set per profile by deployment from boxes_backup_target in infra/group_vars/<profile>.yml, not by the CLI or API. The checked-in dev profile uses file:///var/lib/boxes/data/remote-backups; cloud profiles typically use a gs://.../volume-backups target. Check the selected profile for the exact bucket and prefix.
Create a backup
data, and an optional name. Flags:
--name <NAME>— overrides the positionalname.-m, --message <DESC>— backup description.--no-wait— return immediately instead of blocking.-p, --project <P>-j, --json
create blocks until the backup reaches a terminal state, with a hard-coded 900-second (15-minute) timeout that you cannot override on create. After waiting it prints OK if the status is ready, otherwise FAIL: backup status <status> and exits 1. With --no-wait it returns right away (printing OK or JSON).
When you omit the name, it is auto-generated as bkp-<YYYYMMDDHHMMSS>-<6 hex>.
A backup is full or incremental depending on whether a prior ready backup snapshot exists to use as a parent — the first backup is full, later ones are incremental. Concurrent backup or restore on the same volume is rejected with backup/restore already running for data volume (409).
List and show backups
list takes data; its table columns are NAME STATUS MODE PARENT UPDATED DESCRIPTION, and JSON is {"backups":[...]} with records {name, created_at, updated_at, status, mode?, parent?, description?, error?} where mode is full or incremental. show takes data and backup and displays the fields NAME, STATUS, MODE, PARENT, CREATED, UPDATED, DESCRIPTION, ERROR. Both accept -p, --project and -j, --json.
Wait on a backup
wait takes data and backup positionals, plus --timeout (default 900 seconds), -p, --project, and -j, --json. It prints OK if the backup is ready, otherwise FAIL: backup status <status> and exits 1.
Restore from a backup
zomg data restore has two forms.
Start a restore
--no-wait— return the operation id immediately instead of blocking.--timeout <SECS>— wait timeout (default 900).-p, --project <P>-j, --json
succeeded or failed, then prints OK on success or FAIL: restore status <status> and exits 1. With --no-wait it returns the operation id right away.
A restore downloads the backup chain and receives it, then deletes the current volume subvolume and snapshots the received data into place — so the restore lands on the same named volume.
There is no API to restore a backup into a different volume name. To get data into a new volume, use a local snapshot clone (zomg data create --from … --from-snapshot …).
Query restore status
zomg data restore status <data> <operation-id>. Operation ids have the form restore-<YYYYMMDDHHMMSS>-<6 hex>. Operation records persist, so status keeps working after the restore completes. The table columns are OPERATION STATUS DATA BACKUP UPDATED ERROR, and a restore moves through running → succeeded or failed.
Passing a third positional to the start form (
zomg data restore <data> <backup> <extra>) is an error. Use the explicit status form to query an operation.Related
Boxes
What a box is and how it runs.
Snapshots and bases
Capture and restore box-root state.
Environment
The runtime inside a box.
API reference
The data, backup, and restore routes.