tldr; Zarf's latest release (v0.28.0) contains a few cool improvements to its OCI (Open Container Initiative) library code and CLI options.

Inline Publishing

There is now a new option for the destination of a Zarf package create!

zarf package create . -o oci://

I call this guy inline publishing because it is essentially the same thing as doing a create followed by a publish. This is useful for when you want to create a package and publish it to a registry in one step. In a future release, I plan to combine this with differential packages, so that you can publish a package and only pull images that have changed (saving massive amounts of time and bandwidth).

Slimmer Deployments

Previously, when you did a zarf package deploy oci:// with the --components flag, each and every layer from that package was pulled down before the package could be deployed. This was a bit of a pain, especially when you only wanted to deploy a single component.

Now, that same command will only pull down the layers that are needed for the component(s) you are deploying (this functionality only occurs when you use --confirm, as you might specify other components interactively after the fact).

# only pulls down the layers needed for dubbd-aws (this is just an example)
zarf package deploy oci:// --components dubbd-aws --confirm

Removing an OCI package

Previously, in order to remove a package deployed via zarf package deploy oci://, you had to do a zarf package pull && zarf package remove <tarball>.

Now you can do:

# to remove all components
zarf package remove oci://

# or to remove a specific component

zarf package remove oci:// --components dubbd-aws

The --oci-concurrency flag is now available on all zarf package commands that interact with OCI packages. This allows you to specify the number of concurrent pulls/pushes that can occur at once. It is also accessible via the package.oci_concurrency config value.

Consuming Zarf as a Library

In !1764, I re-wrote how our OCI library code (which uses the ORAS (OCI Registry As Storage) library under the hood) is accessed. Now you can easily do stuff like:

import (


func main() {
    ver := "0.3.0"
    arch := "amd64"
    url := fmt.Sprintf("", ver, arch)

    // create a new client
    // auth checking is handled immediately
    client, err := oci.NewOrasRemote(url)
    if err != nil {
        message.Fatal(err, "Failed to create OCI client")

    dst := filepath.Join("unpacked-dubbd-aws")
    err = utils.CreateDirectory(dst, 0755)
    if err != nil {
        message.Fatal(err, "Failed to create directory")
    // pull the package into dst with a concurrency of 10
    err = client.PullPackage(dst, 10)
    if err != nil {
        message.Fatalf(err, "Failed to pull package %s", client.Reference)

    // contents of the package are now in dst
    // $ ls -R unpacked-dubbd-aws
    // zarf.yaml
    // checksums.txt
    // components/...
    // blobs/sha256/...

There are also a ton of other useful helper functions (like PullPackageMetadata, FetchZarfYaml, LayersFromRequestedComponents etc...) that you can use to build more advanced workflows.

Much of this work was a necessary precursor to the upcoming zarf bundle command and bundler library. I'm excited to share more about that soon!