CLI tool
zarf-dev/zarf avatar
zarf-dev/zarf

zarf says its OS matrix is coming soon, and holds the module graph with two replaces

The Airgap Native Package Manager for Kubernetes. If you want to remove it, you can still use your Helm charts to deploy your software manually.

2,094 stars299 forksGoApache-2.0

At a glance

What is it?
zarf is a statically compiled Go binary for packaging Kubernetes workloads so they can be installed without connectivity. The claims worth checking are the ones the repository understates: the OS support matrix is promised rather than published, the shipped container starts an internal agent at debug log level, the JSON schema validator is a fork pinned to a commit, and sqlite is pinned backwards to avoid a breaking change.
Who is it for?
Adopt zarf if you are deploying Kubernetes into a cluster with no route to a registry, and you want the package to be verifiable rather than trusted by convention, since it produces SBOMs, signs with cosign and publishes to an OCI registry. Do not adopt it if you need a documented platform support matrix today, because the README defers that table to a future release and the container image only builds for Linux.
Can I use it commercially?
Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 1 day ago.
What is it written in?
Mainly Go, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on November 14, 2026, and from our analysis. They are not legal advice.

From daily snapshots

Star history

  • zarf-dev/zarf2,094 ★ · +100
Star history
Repository2026-08-022026-11-14
zarf-dev/zarf2k2.1k

100 Stars in the last 104 days, measured from the daily GitHub snapshot. Compare with other projects

Editorial analysis

The OS support matrix is a promise in a bullet point

One of the reasons to use the tool is stated as OS agnostic, with a qualifier attached: Zarf supports numerous operating systems, and a full matrix of supported operating systems, architectures and feature sets is coming soon.

So the compatibility table is not in the repository. What is in the repository is the build logic, and it describes the actual support surface precisely. The Makefile reads `uname -m`, maps `x86_64` to `amd64`, maps `aarch64` or `arm64` to `arm64`, and falls back to `amd64` for anything else, with an override available as a make argument. It then picks a binary name per platform: `.exe` on Windows, a `-arm` suffix on Linux on arm, `-mac` on Darwin, then `-intel` or `-apple` added for i386 or arm.

That is five naming paths for three architectures and three operating systems, which is real cross-platform support and is simply not written down anywhere a user can find it.

The container image is narrower. It copies `build/zarf-linux-$TARGETARCH`, so the image build covers Linux targets only, and the per-platform suffixes the Makefile computes for Windows and macOS have no image behind them.

Two architectures, three operating systems, one image target, and a matrix that is on its way.

The shipped container starts an internal agent at debug level

The Dockerfile is ten lines and every one of them is a decision:

dockerfile
FROM cgr.dev/chainguard/static:latest
ARG TARGETARCH

USER 65532:65532

COPY --chown=65532:65532 --chmod=0700 "build/zarf-linux-$TARGETARCH" /zarf

CMD ["/zarf", "internal", "agent", "--log-level=debug", "--log-format=console", "--no-color"]

The base is Chainguard's static image, which is why a statically compiled binary is the right thing to copy into it, and the process runs as UID 65532 with a comment recording that this is the `nonroot` user in that image and a link to its documentation. The binary is copied with mode 0700.

The command is the part to notice. Running the image without overriding it starts `zarf internal agent` with `--log-level=debug`, console log format and colour disabled. An internal subcommand is the default entry point, and debug is the default verbosity, which means anyone who runs the image as-is gets verbose internal logging whether or not that was the intent.

That is a reasonable choice for a sidecar or a debugging session and a poor one for a shared log stream. Both flags are overridable at run time, so it is a default rather than a constraint, but it is the default that ships.

Two replace directives hold the module graph in place

go.mod declares `go 1.26.6` and then immediately does something more telling, twice, each with a comment explaining the reason and the exit condition:

go
replace github.com/xeipuuv/gojsonschema => github.com/defenseunicorns/gojsonschema v0.0.0-20231116163348-e00f069122d6

replace modernc.org/sqlite => modernc.org/sqlite v1.32.0

The first carries a TODO attributed to a maintainer and a link to a pull request against gojsonschema, described as pending merge. Until that lands, the schema validator is a fork referenced by a pseudo-version built from a commit timestamp and hash, not a released tag. That pins the exact code, and it also means the validator for this project's own JSON schema lives outside the project's control.

The second is a downgrade rather than a fork. The comment says that once a related sqlite wrapper updates to handle breaking changes in modernc.org/sqlite v1.33.0, the replace can be removed. So the build is deliberately held one minor version behind to avoid a change upstream has already shipped.

Both are honest, documented workarounds with removal conditions attached, which is the right way to write them. The practical consequence for an auditor is that neither dependency version can be read off a tag, and both have to be taken from the file.

The rest of the module list is the feature set in dependency form: Syft and Stereoscope for software bills of materials, k9s for the built-in dashboard, distribution and go-containerregistry for the built-in registry and OCI publishing, Flux source-controller APIs and gitkit, cosign with sigstore-go plus AWS, Azure and GCP KMS backends, go-diff for the component actions, pluto for chart inspection, and pterm for the terminal interface. And the tree carries `vendor/`, so the build itself is hermetic.

The Kubernetes dependency major is the client's major plus one

The Makefile derives Zarf's Kubernetes dependency version from the Kubernetes client library rather than declaring it. It asks `go list` for the version of `k8s.io/client-go`, splits the version on dots, takes the first field and adds one for the major, and uses the second and third fields as the minor and patch.

That is a clever shortcut and a fragile one. The major version of a Kubernetes client library is the API version the client speaks, and Zarf's own needs do not move in lockstep with the client's. Any patch of client-go that changes the minor or patch fields changes Zarf's declared dependency set, and a client-go release that jumps a major would silently move Zarf by one as well.

The same file handles versioning its own binary more conventionally. `CLI_VERSION` comes from `git describe --tags`, and when that produces nothing the fallback string is `unset-development-only`, which is a self-documenting way to mark a build. The value is compiled in with `-s -w -X github.com/zarf-dev/zarf/src/config.CLIVersion=...`, so stripping symbols and stamping the version happen in the same call.

The architecture detection ladder described earlier lives a few lines above all of this, with the comment explaining that the architecture is detected from the system but can be overridden, giving the example of running the end to end tests with `ARCH=arm64`.

A mutating webhook rewrites image paths in running pods

The mechanism that makes an airgap package work is a mutating admission webhook, and the README links to the architecture decision record for it rather than describing it in prose.

What it does is rewrite the things a running pod depends on: the pod's image path and its pull secrets, and the URLs and secret references of Flux GitRepository resources. In other words, Zarf does not ask your manifests to point at a local registry, it changes the requests the API server sees. That is why a package built on a connected machine can install into a disconnected cluster without you editing values, and it is also why the webhook needs cluster-admin level trust: it is a component that alters other people's workloads at admission time.

Two related capabilities sit next to it. There is a built-in command to find images and resources inside a Helm chart, which is what a package author runs to discover what has to travel. And there are component lifecycle actions, whose purpose is to run something at a defined point in a component's life rather than only on install.

The connectivity story is completed by a tunnel command that connects to Kubernetes resources with no network routing, DNS, TLS or Ingress configuration required. For an airgapped cluster that is often the only way to reach anything at all, and it is why the tool can be useful in a network where nothing else is reachable.

cosign, three cloud KMS backends and a public key in the tree

Signature support is not an afterthought here. The module list carries cosign v3 and sigstore-go, and then three separate key management packages, one each for AWS, Azure and GCP, which is what lets a package be signed by a key that lives in a cloud KMS rather than on a laptop.

Zarf is also signed itself, and the verification key is committed. `cosign.pub` sits at the root, so checking a release does not require trusting a key server or a transparency log at install time; it requires trusting a file in the repository, which is a different and, for airgap, a more workable trust decision.

Around the signing sit the supply chain files. `.grype.yaml` configures a vulnerability scanner, and `.vex/` holds vulnerability exploitability exchange documents, which are the artefact that says a known flaw does not actually reach your code path. Both are things an airgap buyer is often required to produce, and both live in the repository rather than in a separate process.

Software bills of materials are a first-class feature in the same way. Syft and Stereoscope generate them during packaging, and the tools can extract an SBOM from a package for analysis with something else. So the same artifact that moves your software also carries the inventory of what is inside it, and Zarf generates a JSON schema for its own package format in the tree, as `zarf.schema.json`.

A charter, a contributor ladder and a second goreleaser config

The governance surface at the root is wider than most projects of this size carry. There is a `CHARTER.pdf` in PDF form, `CODEOWNERS`, a `CONTRIBUTOR_LADDER.md`, `ADOPTERS.md`, `RELEASES.md`, `SUPPORT.md`, `COMMUNITY.md` and an `AGENTS.md`, alongside `CONTRIBUTING.md`, `CODE_OF_CONDUCT.md` and a `SECURITY.md`. The project asks for questions in the Kubernetes Slack channel rather than running its own forum, and it points at an architectural diagram and a Nerd Notes section for anyone who wants the internals.

There is also a second build configuration: `.goreleaser-pro.yaml` beside `.goreleaser.yaml`. Goreleaser Pro is a commercial product, so the release pipeline for this free tool leans on paid tooling, which is worth knowing if you are reproducing the build. Alongside it sit `release-please-config.json` with a manifest, `renovate.json` for dependency updates, a commitlint config, a markdownlint config and a pre-commit config.

Examples are first class, with sixteen directories under `examples/`: Argo CD, component actions, composable packages, the config file, DOS games, git data, Helm charts, Kiwix, Longhorn, plain manifests, package flavors, Flux with Podinfo, values templating, variables, WordPress, and one called `yolo`.

The provenance is stated plainly too. Early research and prototypes were developed jointly with the United States Naval Postgraduate School, with the underlying work published as a thesis.

Releases move quickly at this stage: v0.87.0 and its release candidate landed about half an hour apart on 2026-10-01, and the branch was pushed again the following day.

Editorial conclusion

Adopt zarf if you are deploying Kubernetes into a cluster with no route to a registry, and you want the package to be verifiable rather than trusted by convention, since it produces SBOMs, signs with cosign and publishes to an OCI registry. Do not adopt it if you need a documented platform support matrix today, because the README defers that table to a future release and the container image only builds for Linux. Before you rely on it, read the two replace directives in go.mod, since one holds the schema validator at a fork commit and the other holds sqlite at an older major-compatible release, and check the default log level of the shipped image before you run it somewhere people read logs.

Frequently asked questions

What is a zarf used for?

Zarf is the airgap native package manager for Kubernetes. It packages the parts of the internet an application needs, including images and Helm charts, into a single compressed file that installs without connectivity, and it supports disconnected cluster creation with K3s or deployment into an existing cluster through a kube config.

How does zarf work?

You build a package while connected, describing components, actions, variables and the images to include, then publish it as an OCI artifact and deploy it in the disconnected cluster. Zarf pre-pulls the images into its registry and a mutating admission webhook rewrites pod image paths, pull secrets and Flux GitRepository URLs so the workloads resolve against the local copies.

Does Zarf have vendor lock-in?

The project states there is no proprietary software that locks you in, and that if you want to remove Zarf you can still use your Helm charts to deploy your software manually. The CLI is a statically compiled binary with no runtime dependencies, and the licence is Apache-2.0.

Which operating systems does Zarf support?

The README says Zarf supports numerous operating systems and that a full matrix of supported operating systems, architectures and feature sets is coming soon, so no matrix is published yet. The Makefile shows the real surface: amd64 and arm64, with Linux, Windows and macOS binary variants, and the container image is built for Linux targets only.

How does Zarf sign and verify packages?

With cosign and sigstore-go, including AWS, Azure and GCP key management backends so signing keys can live in a cloud KMS. Zarf releases are signed too, and the verification key is committed at the repository root as cosign.pub, so verification does not need a key server or a network.

What does Zarf's container image run by default?

The Dockerfile sets its command to `/zarf internal agent --log-level=debug --log-format=console --no-color`. It runs as UID 65532 on the Chainguard static base with the binary copied in mode 0700, so debug logging and an internal subcommand are the out of the box behaviour unless overridden at run time.

Official sources

  1. Official documentation
  2. Official README
  3. Project repository
  4. Release notes
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/zarf-dev-zarf.svg)](https://hysenlabs.com/projects/zarf-dev-zarf)

Star history chart for your README

markdown
[![Star history](https://hysenlabs.com/chart/zarf-dev-zarf.svg)](https://hysenlabs.com/projects/zarf-dev-zarf)

Was this useful?