OCI tags

Full and thin tags (Portable Thin Tags) in an OCI registry. Pull standard OCI filesystem layers onto a VM, and push your own tools as thin tags (Anka 3.10.0 and later)

Anka is compatible with the OCI Container Registry Protocol. A tag that you store in an OCI registry is an OCI tag. An OCI tag is one of two types:

  • A full tag includes the macOS disk. It is the same as a tag in the Anka Registry. A VM can start from a full tag alone. Anka 3.9.0 and later can push and pull full tags with an OCI registry.
  • A thin tag holds only file layers, such as the files of one tool. It does not include the macOS disk. Anka 3.10.0 and later can push and pull thin tags.

Marketing calls these thin tags Portable Thin Tags. In Anka they are thin tags: standard OCI filesystem layers (file-diff tarballs) without the macOS disk. “Thin” means less to store and less to move. “Portable” means you can pull the same tag onto compatible VMs. --thin on push takes only the last layer, not the full chain, and still produces a fully compliant OCI repository and tag. Third-party tools—malware and vulnerability scanners, software inventory, and OCI clients such as skopeo and oras—can process that layer. There is no vendor lock on the layer format.

As expected with the OCI Container Registry Protocol in other tools, you can pull a thin tag from one VM 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 thin tag in an OCI registry, and pull those tags when the VM needs them.

A tool update still means composing the thin-tag stack into each final template or tag: pull the thin tags onto the base, then push the finished tag. You do not need to start the VM to reinstall the tool.

You can also pull public OCI artifacts onto a VM. These artifacts are thin tags. Examples: Homebrew bottles from ghcr.io, GGUF models from Hugging Face (oci://hf.co/...), and other OCI-compliant layers such as Ollama image layers (application/vnd.ollama.image). See Pull OCI tags.

Store thin tags in an OCI registry. Use an oci:// URL or an https:// URL whose path contains /v2/. The Anka Registry does not store thin tags. A push to the Anka Registry ignores push_mode and --thin, and always stores a full tag.
Compression for the OCI Container Registry Protocol 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 thin tags stacked on VMs with different macOS versions

Before you mix thin tags with full tags, read Full tags and thin tags.

Pull OCI tags

Pull more than one thin 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 go back to the state before the thin tag pull, pull a full tag again. The full tag can come from the Anka Registry or from an OCI registry.

Pull a GGUF model from Hugging Face

Starting in version 3.10, Anka can pull GGUF models that Hugging Face and Ollama publish as OCI artifacts. The flow is the same as a Homebrew bottle: give anka pull the VM name and the oci:// URL. Anka applies standard tar layers and layers with media type application/vnd.ollama.image.

❯ anka pull 27.0.1 oci://hf.co/MaziyarPanahi/Llama-3.2-1B-Instruct-GGUF:Q4_K_M

The text after the colon is the quantization, such as Q4_K_M. Anka stores the model under the guest user’s Hugging Face Hub cache (by default ~/.cache/huggingface/hub). A later job can load the weights from that cache. The job does not need a large download from the internet at run time.

Set ANKA_MODEL_CACHE to a path relative to the guest home directory when you need a different cache root.

The Homebrew and Hugging Face examples above both use anka pull <vm> oci://.... Homebrew often needs --transform so the bottle lands on PATH. A GGUF model from hf.co does not need --transform.

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

Use --thin when you push a tool as a thin tag. --thin pushes only the last layer, so you do not change push_mode. Leave push_mode at 1 (the default). Then a push without --thin still makes a full tag that includes the macOS disk.

push_mode 2 is another way to make a thin tag. It stores only the file changes since the base tag. It needs a config change or ANKA_PUSH_MODE=2 on the command. A push to the Anka Registry does not use --thin or push_mode. The Anka Registry always stores a full tag.

The example below pushes the macOS base as a full tag. It then leaves push_mode at 1 and uses --thin to push each tool as a thin tag. For push_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

Install one tool, stop the VM, and push the next tag with --thin. Leave push_mode at 1. 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 --thin 27.0.1 -t xcode-27.1 oci://registry.example.com/v2/

The xcode-27.1 tag is a thin tag. It holds only the last layer: the files that changed after 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. Push cmake with --thin so that tag holds only the CMake layer. push_mode 2 is another way to store only the file changes. See Push modes.

With --thin

--thin on the push stores only the last layer. The cmake tag is then a thin tag that holds the CMake files. The Xcode files stay in the xcode-27.1 tag. Pull xcode-27.1 first when the VM also needs Xcode. Pull the vanilla tag first when the host does not have that tag.

❯ anka push --thin 27.0.1 -t cmake 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 thin tag locally as 27.0.1--xcode-27.1 and 27.0.1--cmake.

--thin on a pull applies only the last layer in that tag. Use it when a tag still holds more than one layer, and you want only the last one:

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

Without --thin

Without --thin, the push writes every diff after the base disk into the cmake tag. 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

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-oci-tag-editor is a separate terminal tool. It lets you browse a thin 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-oci-tag-editor --vm <name>. The editor opens the top layer of the VM’s current tag. When you save, it commits the edits as a new local tag. Push that tag with --thin to store it as a thin tag.

Build a VM from OCI tags