OCI tags

Pull file layers from any OCI registry onto a VM, and push your own tools as OCI tags (Anka 3.10.0 and later)

Anka is now compatible with OCI layers. They are related to Anka Template Tags, so we’ll call them OCI Tags for the rest of this guide.

As expected with OCI layers with other tools, an OCI tag from one VM can be pulled onto any other VM. The other VM can have a different name, a different macOS version, or live on another host. Keep the vanilla macOS disk as a full tag in the Anka Registry or in an OCI registry, and pull that tag when a host needs the foundation. Store each tool as its own OCI tag in an OCI registry, and pull those tags when the VM needs them.

Store OCI tags in an OCI registry. Use an oci:// URL or an https:// URL whose path contains /v2/. The Anka Registry does not store these types of tags. A push to the Anka Registry ignores push_mode and --thin, and always stores the full tag.
OCI layers compression can be controlled with the ANKA_COMPRESSION_LEVEL environment variable. Setting it to 1 turns on gzip and sets zlib’s level to 1 (fastest / lightest compression).

The same OCI tags stacked on VMs with different macOS versions

Before you mix OCI tags with Anka Registry tags, read Anka Registry tags and OCI tags.

Pull OCI tags

Pull more than one OCI tag onto the same VM.

Later pulls can come from other OCI registries.

Let’s use Homebrew as an example. Homebrew publishes bottles as OCI archives. This means we can pull cmake onto an Anka VM without installing Homebrew, adding a symlink, or changing PATH.

By default, a bottle stores its files under a version directory such as cmake/4.3.4. Anka’s --transform option rewrites that prefix to usr/local, so cmake is exactly where you want it for the default PATH to see it.

❯ anka pull 27.0.1 oci://ghcr.io/homebrew/core/cmake:4.3.4 --transform 's|cmake/4.3.4|usr/local' --dry-run
usr/local/
usr/local/.brew/
usr/local/.brew/cmake.rb
usr/local/LICENSE.rst
usr/local/README.rst
usr/local/bin/
usr/local/bin/ccmake
usr/local/bin/cmake
usr/local/bin/cpack
usr/local/bin/ctest
. . .
anka pull 27.0.1 oci://ghcr.io/homebrew/core/cmake:4.3.4 --transform 's|cmake/4.3.4|usr/local'
100% [||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||]          
9eec878a-7f16-492f-97e9-88e7247e43a5: pulled 19.20MiB for tag cmake--4.3.4

You’ll see the files in the VM’s /usr/local directory.

You’ll also see the tag locally under anka list and anka show commands.

❯ anka show 27.0.1 tag
+--------------+--------------------+----------------------+---------+----------+
| tag          | name               | creation_date        | state   | size     |
+--------------+--------------------+----------------------+---------+----------+
| vanilla      | 27.0.1             | Oct 12 11:48:18 2026 | stopped | 29.45GiB |
+--------------+--------------------+----------------------+---------+----------+
| cmake--4.3.4 | 27.0.1             | Oct 2 15:19:08 2026  | stopped | 256MiB   |
+--------------+--------------------+----------------------+---------+----------+

If I wanted, I could put a default in /usr/local, then install a different version of cmake into /usr/local, but with the versions folder.

❯ anka pull 27.0.1 oci://ghcr.io/homebrew/core/cmake:4.4.4 --transform 's|cmake|usr/local/cmake' --dry-run
usr/local/cmake/4.4.4
usr/local/cmake/4.4.4/.brew/
usr/local/cmake/4.4.4/.brew/cmake.rb
usr/local/cmake/4.4.4/LICENSE.rst
usr/local/cmake/4.4.4/README.rst
usr/local/cmake/4.4.4/bin/
usr/local/cmake/4.4.4/bin/ccmake
usr/local/cmake/4.4.4/bin/cmake
usr/local/cmake/4.4.4/bin/cpack
usr/local/cmake/4.4.4/bin/ctest
❯ anka pull 27.0.1 oci://ghcr.io/homebrew/core/cmake:4.4.4 --transform 's|cmake|usr/local/cmake'
100% [||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||]          
9eec878a-7f16-492f-97e9-88e7247e43a5: pulled 19.24MiB for tag cmake--4.4.4
❯ anka show 27.0.1 tag
+--------------+--------------------+----------------------+---------+--------+
| tag          | name               | creation_date        | state   | size   |
+--------------+--------------------+----------------------+---------+--------+
| cmake--4.3.4 | 27.0.1             | Jun 19 00:32:17 2026 | stopped | 256MiB |
+--------------+--------------------+----------------------+---------+--------+
| cmake--4.4.4 | 27.0.1             | Oct 2 15:19:08 2026  | stopped | 231MiB |
+--------------+--------------------+----------------------+---------+--------+
Deletion of a tag from the VM (like anka delete 27.0.1 --tag cmake--4.4.4) does NOT remove the data from the VM. To revert to the state prior to the OCI tag pull, you need to have an Anka Registry tag to revert to with a pull.

Pull without a VM name

A pull that is only a URL creates the VM for you. Point default_vmid at a VM you already have. A UUID is the stable choice. Add :<tag> to start from that tag.

❯ anka config default_vmid <vm-uuid>
❯ anka pull oci://ghcr.io/homebrew/core/cmake:4.3.4 --transform 's|cmake/4.3.4|usr/local'

Anka creates a VM named cmake from that VM and pulls the bottle into usr/local. You do not need to install Homebrew, add a symlink, or change PATH. A job that only has the registry URL can use this form.

Push OCI tags

Machines that push to an OCI registry need the right push_mode. push_mode 1 pushes the full tag, including its base tag. push_mode 2 pushes only the changes since the base tag. A push to the Anka Registry does not use push_mode. The Anka Registry always stores the full tag.

The example below uses push_mode 1 for the macOS tag and push_mode 2 for the tool tags. For details on each mode, see Push modes.

Push the macOS base tag

Create a new VM. anka create installs macOS only. Leave push_mode at 1 for this push. 1 is the default. This push uploads the macOS disk as a full tag to the OCI registry.

❯ anka create 27.0.1 27.0.1
❯ anka push 27.0.1 -t vanilla oci://registry.example.com/v2/

An OCI registry URL has a /v2/ path. Leave the repository name off the URL. Anka uses the VM name. This push creates repository 27.0.1 and tag vanilla. A new 27.0.1 VM reports about 29.82GiB uploaded to tag vanilla. Pull this tag when a host needs the macOS disk:

❯ anka pull 27.0.1 oci://registry.example.com/v2/27.0.1:vanilla
To keep the macOS base tag in the Anka Registry instead, see Combining both registries.

Push the tool tags

Set push_mode to 2 before you push the tool tags.

❯ anka config push_mode 2
ANKA_PUSH_MODE sets the mode for one command. Use it when a change to anka config is a problem. Example: ANKA_PUSH_MODE=2 anka push 27.0.1 -t xcode-27.1 oci://registry.example.com/v2/

Install one tool, stop the VM, and push the next tag to the OCI registry. Leave the repository name off the URL. Anka uses the VM name.

❯ anka start 27.0.1
# install Xcode in the VM, then stop it
❯ anka stop 27.0.1
❯ anka push 27.0.1 -t xcode-27.1 oci://registry.example.com/v2/

The xcode-27.1 push uploads only the file changes since vanilla.

Install CMake and stop the VM.

❯ anka start 27.0.1
# install CMake in the VM, then stop it
❯ anka stop 27.0.1

The tag chain on this VM is now the vanilla tag, then the xcode-27.1 tag, then the CMake changes. You can push cmake without --thin or with --thin. Note that --thin works with push_mode 1 and with push_mode 2.

Without --thin

Without --thin, the push writes every diff after the base disk into the cmake tag (which is the current behavior customers are used to). The first layer is the vanilla tag’s own diff. On this VM that diff has no file changes, so the layer is a 1024-byte tar with no files. The next layer is the Xcode diff. The last layer is the CMake diff. The pull reads only the cmake tag. It does not fetch the xcode-27.1 tag. The Xcode files are in the cmake tag.

Pull cmake onto a second VM that is a clone of vanilla. Anka copies the Xcode files and the CMake files onto that VM. A second pull of the same tag prints already up-to-date.

❯ anka push 27.0.1 -t cmake oci://registry.example.com/v2/
❯ anka pull 15.6.1 oci://registry.example.com/v2/27.0.1:cmake

With --thin

anka pull --thin applies only the last layer in that tag. On this cmake tag, that layer is the CMake diff. The Xcode diff in the same tag is skipped. On a clone of vanilla, this pull copies the CMake files and leaves the Xcode files out.

❯ anka pull 15.6.1 --thin oci://registry.example.com/v2/27.0.1:cmake

With --thin on the push, the cmake tag stores only the CMake diff. The Xcode diff stays in the xcode-27.1 tag. A pull of cmake applies the CMake layer alone. Pull xcode-27.1 first when the VM also needs Xcode. Pull the vanilla tag first when the host does not have that tag, as shown in the first step.

❯ anka push 27.0.1 -t cmake --thin oci://registry.example.com/v2/
❯ anka pull 15.6.1 oci://registry.example.com/v2/27.0.1:xcode-27.1
❯ anka pull 15.6.1 oci://registry.example.com/v2/27.0.1:cmake

Anka stores each pulled OCI tag locally as 27.0.1--xcode-27.1 and 27.0.1--cmake.

A later push of that VM with push_mode 2, after one --thin pull, stores two file layers. One layer is the changes already on the VM. The other layer is the thin tag. The base tag, such as vanilla, stays out.

❯ anka push 15.6.1 -t cmake-on-15 oci://registry.example.com/v2/
❯ anka -j registry show oci://registry.example.com/v2/15.6.1:cmake-on-15 --manifest

Push onto an existing template

To publish those tools onto a template that is already in the registry, put that template name in the URL. This is the same as --remote-vm / -v. Use -f when the local tag already exists.

❯ anka push builder -f -t xcode-brew https://registry.example.com/v2/shared

With push_mode 2, if shared is a different VM than builder and it already has tags, this adds the new software onto shared’s newest tag. To add onto one tag by name:

❯ anka push builder -t xcode-brew https://registry.example.com/v2/shared:tools

A push_mode 2 push creates tools when that tag is absent. The registry tag name is tools. The local tag name stays xcode-brew.

Move OCI tags without a registry

To move an OCI tag between hosts with anka export and anka import, see Export and import an OCI tag.

Edit a layer before you push it

anka-layer-editor is a separate terminal tool. It lets you browse an OCI tag layer and change it before you push it. You can remove files, add files from the host, and add whiteouts that delete a path from the base. You build the tool from that repository.

You can use it in two ways:

  • On an exported archive. Export the layer with anka export --layer, open the .tar.gz in the editor, then import the edited archive.
  • On a stopped VM. Run anka-layer-editor --vm <name>. The editor opens the top layer of the VM’s current OCI tag. When you save, it commits the edits as a new local OCI tag.

Build a VM from OCI tags