What "Save as Base Image" actually gives you
Stop a workspace, and its detail view offers Save as Base Image: a confirmation sheet with one editable text field, pre-filled with the workspace's own name, and a Save button. Click it, and the app copies the workspace's disk into ~/Library/Application Support/VeloWorkspaces/BaseImages/ using an APFS copy-on-write clone, then marks that copy immutable at the filesystem level — a real chflags-style flag, not just an app-level "don't touch this" convention. For a macOS guest, the same seal also copies its hardware.bin, machine.bin, and auxiliary.img platform files into a matching metadata folder beside the disk, the same identity files covered in the macOS guests post — skip that step and a clone fails to boot with "macOS hardware model data not found."
That's genuinely the whole mechanism. There's no build step, no provisioning script the app runs on your behalf, no manifest describing what's inside. A base image is a sealed, read-only copy of a disk you configured by hand, plus a small metadata record: a display name, the OS family and version, the file size, a creation timestamp, and — for reference only — the name of the workspace it was sealed from.
Re-sealing never overwrites, and renaming isn't an option
Two behaviors are worth knowing before you rely on this for anything real. First: saving the same workspace as a base image a second time creates an entirely independent record and file, not an overwrite of the first one. That's deliberate — an early version of this feature named the destination file after the source workspace's ID, so a second seal of the same workspace tried to overwrite a file the first seal had just made immutable, which either failed outright or silently clobbered a base image other workspaces might already be cloning from. The fix was to name every sealed file after the base image record's own ID instead, so every "Save as Base Image" click produces a new, independent generation, however many times you reseal the same source.
Second: that name field is a one-shot decision. Preferences → Base Images lists what you've sealed — name, OS, size, and "created <date> from <workspace>" — with exactly two actions per row: Delete and Export. There is no rename. Whatever you type in that confirmation sheet is the name that base image carries for as long as it exists, so it's worth treating that field as the one moment version information actually gets recorded, not an afterthought you'll clean up later.
Which means your naming convention is the only version control you get
The sourceWorkspaceDisplayName field looks like lineage tracking, but it isn't — it's a single, flat pointer to the workspace a base image was sealed from, captured once, for display. It does not chain. Clone a workspace from base image A, install a new toolchain on it, and seal that as base image B, and B's record points to the workspace you customized, not to A. Nothing anywhere records "B was derived from A." Look at Preferences → Base Images a few months into a real project and it's a flat, sorted-by-date list of names — nothing groups related generations, nothing shows what changed between two entries with similar names.
That's not a missing feature so much as a deliberate scope boundary: the app seals a disk and clones from it, and stops there. Anything resembling version history has to live in the one field you actually control — the display name — because nothing else will remember it for you. A convention that has worked well in practice:
| Part | Example | Why it's there |
|---|---|---|
| OS + version | ubuntu-24.04 |
Which official image this ultimately traces back to |
| Role | ci-runner |
What this base image is for, not just what OS it runs |
| What changed from the last one | node22 |
The one piece of context sourceWorkspaceDisplayName can't give you back |
| Date sealed | 2026.09 |
Sorts correctly next to every other base image in the flat list |
Strung together: ubuntu-24.04-ci-runner-node22-2026.09. Reseal the same lineage next month with an updated dependency and it becomes ubuntu-24.04-ci-runner-node22-2026.10 — sealed as a brand-new, independent record either way, so the only thing distinguishing "this is the same lineage, one generation later" from "this is an unrelated image that happens to share a prefix" is that the name says so.
"Patch in place" isn't actually an option here — which simplifies the decision
The QA/DevOps post framed this as "cut a new one vs. patch in place," which is the right question for tools that let you mutate a golden image directly. It doesn't quite apply to Velo Workspaces: a sealed base image is filesystem-immutable, so there is no in-place patch operation to reach for in the first place. The only way to change anything is to clone a workspace from the current base image, make the change inside that workspace, and seal the result as a new record. Every change is a new generation, always — which removes a decision that trips people up with other tools (mutate the golden image and risk two people's clones now booting differently) at the cost of a decision this app leaves entirely to you: whether that new generation's name makes its relationship to the last one obvious, or just adds another unlabeled entry to the list.
The cost of that discipline is low specifically because sealing and cloning are both APFS copy-on-write operations rather than full rebuilds — sealing a workspace's disk and cloning from a base image both use clonefile() under the hood, which completes near-instantly on the same volume rather than copying data byte for byte. Cutting a new generation costs you a naming decision, not meaningful time or disk pressure at the moment you do it.
Pruning is manual too — and each entry is a full copy, not a diff
Nothing expires a base image automatically, and there's no "unused in 90 days" prompt. Each one is a complete, standalone copy of a disk, not a layer or a diff against another base image, so an accumulating list of generations costs real, visible space — the size shown next to each entry in Preferences → Base Images is the actual disk usage, not an estimate. A workable discipline: keep the most recent generation or two per name-prefix plus one deliberately pinned "known good" (name it accordingly — ubuntu-24.04-ci-runner-node22-stable, say — so it doesn't get lost in the timestamp sort), and delete the rest from that same Preferences pane once you've confirmed nothing still depends on them.
Export exists for the same reason backups exist anywhere: Preferences → Base Images can export the selected image's disk to a file — raw format only, which is the one format a base image has — useful for archiving a generation outside the app's own managed directory before you delete it, or moving one to another Mac.
What to actually do
- Get the name right at seal time. There's no rename afterward — the confirmation sheet's text field is the only chance to record what this base image is and how it relates to the last one.
- Put OS, role, what changed, and a date in the name, in that order or your own consistent order — since the app tracks none of it, the name is the only place that survives.
- Treat every reseal as a new, independent generation, because that's what it actually is — there's no in-place patch to reach for instead, and no relationship to the previous generation beyond what the name says.
- Prune manually and on purpose. Nothing expires automatically, and each entry is a full copy costing real disk space — keep recent generations plus one pinned known-good per lineage, export before deleting anything you might need later.
This extends the base-image half of the QA/DevOps disposable VM workflow post — that post covers why sealing a base image once and cloning a disposable workspace per run fixes flaky-test and non-idempotent-script bugs at the source; this one covers keeping the base images themselves manageable as they accumulate. If you're building a Linux base image in the first place, the guest OS performance tuning post covers settings worth baking into it before you seal — I/O scheduler, zram vs. swap, filesystem journaling — rather than re-tuning every clone by hand. And if macOS is one of the guest types you're sealing, see what's different about a macOS base image specifically, including why its identity lives beside the disk rather than inside it. For the daily-development side of base images rather than the QA/testing side, see one workspace per project, and handing a teammate the exact same environment via export. Or download Velo Workspaces and try it yourself.