What Save as Base Image gives you

Stop a workspace and its detail view offers Save as Base Image (a Pro feature): a sheet with one text field, pre-filled with the workspace's name, and a Save button. The app then clones the workspace's disk into ~/Library/Application Support/VeloWorkspaces/BaseImages/ with APFS copy-on-write and marks the copy immutable at the filesystem level, a real file flag rather than an app convention. For a macOS guest it also copies the hardware.bin, machine.bin and auxiliary.img platform files into a metadata folder beside the disk, the identity files the macOS guests post describes. Without them, a clone won't boot.

That's the whole mechanism. No build step, no provisioning script, no manifest of what's inside. A base image is a read-only copy of a disk you configured, plus a small record: a name, the OS and version, the size, when it was created and, for reference, the workspace it came from.

Resaving never overwrites, and there's no rename

Two behaviors to know before relying on this. First, saving the same workspace as a base image again creates a new, independent image; it never overwrites the earlier one, which other workspaces may still be cloning from. Every save is a new generation.

Second, the name is a one-time decision. Settings › Images › Base Images lists what you've saved (name, OS, size, and "created <date> from <workspace>") with two actions per image: Delete and Export. There's no rename. Whatever you type in that sheet is the name for as long as the image exists, so treat it as the one moment version information gets recorded.

So your naming convention is your version control

The "from <workspace>" field looks like lineage, but it isn't. It's one flat pointer to the workspace an image was saved from, recorded once, for display. It doesn't chain. Clone a workspace from base image A, add a toolchain, save it as B, and B points to the workspace you customized, not to A. Nothing records that B came from A. A few months into a project, the Base Images list is a flat, date-sorted list of names, with nothing grouping related generations or showing what changed.

That's a deliberate boundary: the app saves a disk and clones from it, and stops there. Version history has to live in the one field you control, the name. A convention that has worked well:

PartExampleWhy it's there
OS and versionubuntu-24.04Which official image it traces back to
Roleci-runnerWhat it's for, not just what it runs
What changednode22The context the "from" field can't give you
Date saved2026.09Sorts correctly in the flat list

Together: ubuntu-24.04-ci-runner-node22-2026.09. Resave next month with an updated dependency and it's ubuntu-24.04-ci-runner-node22-2026.10. Both are independent records, so the only thing that says "same line, one generation later" rather than "unrelated image with a similar name" is the name itself.

There's no patching in place, which simplifies things

With tools that let you modify a golden image directly, the question is whether to cut a new one or patch the old. That doesn't arise here: a saved base image is immutable, so there's no in-place patch. To change anything, clone a workspace from the current image, make the change there, and save the result as a new image. Every change is a new generation. That removes a classic problem (someone patches the golden image and two people's clones now boot differently) and leaves you one decision: whether the new generation's name makes its relationship to the last one obvious.

It's cheap to do, because saving and cloning both use APFS copy-on-write (clonefile()), which completes almost instantly on the same volume. A new generation costs a naming decision, not time or disk space at the moment you make it.

Pruning is manual, and each image is a full copy

Nothing expires a base image, and there's no "unused for 90 days" prompt. Each one is a complete, standalone disk, not a layer on top of another, so generations add up, and the size shown next to each is real disk use. A workable habit: keep the latest one or two generations per name prefix plus one deliberately pinned known-good (name it so, for example ubuntu-24.04-ci-runner-node22-stable, so it doesn't get lost in the date sort), and delete the rest once nothing depends on them.

Export exists for the same reason backups do: it writes the image's disk to a raw file, so you can archive a generation outside the app before deleting it, or move it to another Mac.

A tip since 1.2: the downloaded cloud images and installer ISOs you build base images from sit in the same Settings › Images pane. If you're pruning for space, check that list too.

What to do

  • Get the name right when you save. There's no rename afterwards.
  • Put the OS, role, what changed and a date in the name, in a consistent order. The app tracks none of it, so the name is the only record.
  • Treat every resave as a new, independent generation, because it is.
  • Prune on purpose. Keep recent generations plus one pinned known-good per line, and export before deleting anything you might need.

This extends the base-image half of the QA and DevOps workflow. If "base image", "snapshot" and "clone" have started to blur, this post puts them side by side. Before saving a Linux base image, guest OS performance tuning covers settings worth baking in. For macOS base images, see why a macOS guest's identity lives beside its disk, and for the daily-development side, one workspace per project, and handing a teammate the same environment. Or download Velo Workspaces and try it yourself.