apko drops RUN from the build and gets reproducible images because of it
Build OCI images from APK packages directly without Dockerfile
At a glance
- What is it?
- apko builds OCI images straight from apk packages with no Dockerfile and no RUN statements, which is what lets it claim bitwise reproducible output and a complete SBOM. The cost is that anything bespoke must already exist as an apk package, so the real adoption question is where your custom binaries come from.
- Who is it for?
- Adopt apko if your images are assembled from existing apk packages and you need bitwise reproducible output plus an SBOM that covers the whole filesystem, which is exactly the case for CI rebuilding images on every upstream patch. Do not adopt it if your build needs to compile, generate or download anything during the image build, because apko has no RUN equivalent and refuses to pretend otherwise.
- 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 received new commits within the last day.
- What is it written in?
- Mainly Go, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The missing RUN statement is the whole design
apko builds OCI container images from apk packages without a Dockerfile. The absence of arbitrary commands during the build is deliberate, and the README states it plainly: by design, apko does not support an equivalent of RUN statements in Dockerfiles.
That single decision explains the properties the project advertises. If the only inputs are a package list and a repository URL, then the contents of the image are fully determined, and apko can make stronger statements about what is inside than a Dockerfile-based build can. Two follow from directly: the images are fully bitwise reproducible, and SBOMs can cover the complete contents. Reproducibility is stated as a default guarantee rather than a flag. Run apko twice and you get exactly the same binary.
The other advertised properties come from the same source. Images are small because they contain only what the application needs, in the distroless style. Builds are fast because there is no command execution, with the goal stated as building images in ms, though the README gives no measurement behind that figure. An SBOM is produced detailing all the packages inside. And for containers that need several processes, apko supports the s6 supervision suite to run them without reaping or signalling issues, which is the problem you hit when a shell-form entrypoint and an init-less container disagree about who reaps orphans.
The design was heavily influenced by ko and distroless, and apko was created by Chainguard, which needs secure, reproducible images for its tooling and has to rebuild constantly as new versions and patches land.
A YAML file with a repository, a package list and an entrypoint
The image definition is a YAML file. The README's quickstart uses the Alpine base example, which is small enough to read in full.
contents:
repositories:
- https://dl-cdn.alpinelinux.org/alpine/v3.22/main
packages:
- alpine-base
entrypoint:
command: /bin/sh -l
# optional environment configuration
environment:
PATH: /usr/local/sbin:/usr/local/bin:/usr/bin:/usr/sbin:/sbin:/binFour things are happening. contents.repositories lists the APK repositories to resolve packages from, here one URL pinning Alpine 3.22 main. contents.packages is the list of package names, here a single metapackage called alpine-base. entrypoint.command is given as a single string, /bin/sh -l, which matters because apk packages do not carry entrypoint metadata the way a Dockerfile's ENTRYPOINT does. environment is explicitly marked optional, and PATH is spelled out in full rather than inherited.
The apko file format supports considerably more than this. The examples directory in the repository names the shape of the surface: per-architecture files such as alpine-amd64.yaml, a rootless variant, python3 and go images, nginx in both root and rootless form, wolfi-base.yaml and wolfi-go.yaml for the Wolfi distribution, certificates and package-certificates, a runtime-keyring example, an alias-only file, an include file, an overlay file, an options file, a dev environment file, an on_top_of_base directory, an old-glibc example, and abseil-regression.yaml. Those filenames alone tell you the format handles architecture pinning, keyring and certificate management, overlays, includes, aliases and building on top of an existing base. The format itself is documented at docs/apko_file.md.
The command surface has two verbs that matter. apko build writes a tarball you then load or push by hand. apko publish talks to a registry directly.
Four install routes and a container-first workflow
apko is a Go program distributed four ways, and the README lists all four.
brew install apkomise use -g apko@latestThe mise invocation is the one to note for reproducibility work, since it takes an explicit version, and the README notes the version is specified there with latest used as the sample. Installing from source is a plain go install against the module path:
go install chainguard.dev/apko@latestAnd for machines where you would rather not install a binary at all, the project publishes its own image:
docker run cgr.dev/chainguard/apko versionBecause the container has no access to your filesystem by default, the README explains that to use the examples you generally want to mount your current directory into the container, which is why the build command in the containerized flow carries a volume flag and a /work path:
docker run -v "$PWD":/work cgr.dev/chainguard/apko build examples/alpine-base.yaml apko-alpine:edge apko-alpine.tarNote the shape of that command, because it is the same everywhere: a config file, an image name, and an output path. Once the tar exists you load it into Docker with docker load, and Docker reports the loaded image name. To run it you append your processor architecture to the tag, which the README writes as the image tag followed by a hyphen and the architecture. That detail is a documentation placeholder rather than a literal string, so expect to substitute your own architecture.
Build, load, run, or publish straight to a registry
The local path takes three commands once you have the binary and a config file.
apko build examples/alpine-base.yaml apko-alpine:test apko-alpine.tarThe output is worth reading, because the log lines tell you what apko actually produces. It generates an SBOM, it reports that it is building an OCI image from a layer tarball in a temporary directory, it prints both the OCI layer digest and the layer diffID as sha256 values, and it confirms the output image file path. Two digests rather than one is normal OCI behaviour, since a diffID covers the uncompressed layer content and the digest covers the compressed layer, and having both in the log makes the build verifiable afterwards.
Loading the result into Docker is a shell redirection, and the confirmation Docker prints is the image name you chose:
docker load < apko-alpine.tarPublishing is a single verb with no intermediate file:
apko publish examples/alpine-base.yaml myrepo/alpine-apko:testThe README states that this can be done from any environment with apk tooling, which is a stronger requirement than it sounds. Because the build resolves packages from remote repositories and unpacks their contents, you need apk tooling available locally to do that work, which is why the alpine-base example works on macOS and on Alpine itself but the same config would need adjustment for a distribution whose package manager is not apk-based. Cross-building for another architecture is what the per-architecture example files are for, since the example name alpine-amd64.yaml exists precisely because the package set differs by target.
Where apko stops and melange starts
The limitation is stated in the declarative section and it is not a bug. To install bespoke tooling or applications into an image, they must first be packaged into an apk. There is no way around this inside apko, because allowing arbitrary commands would undo the reproducibility guarantee.
The workaround is the sister project, melange, which is designed to produce the apk packages apko consumes. The README's position is that melange and apko together cover the vast majority of container image use cases, and where they are not a good fit the recommendation is a hybrid: build a base image with apko and melange, then use traditional tooling such as Dockerfiles for the final step.
That hybrid advice is the honest answer for most teams with a compiled application. Building a Go binary needs the toolchain, and the toolchain has to come from somewhere. The realistic sequence is melange packages the binary, apko assembles the runtime image around it, and you accept that the actual compile happened outside the image build.
The examples directory shows what that looks like in practice. examples/wolfi-go.yaml and examples/alpine-python3.yaml are language images, and examples/on_top_of_base/ is a directory, which suggests the layering capability has its own set of examples separate from single-file definitions.
There is a README notice that apko is a work in progress and details are subject to change, and the repository carries a NEWS.md alongside release.md and a CHANGELOG-style history. Given release numbering, the project ships frequently: v1.4.9, v1.4.10 and v1.4.11 all landed within the first half of October 2026, and the last push was on 2026-10-15.
The build itself is reproducible down to the build date
The Makefile is where the reproducibility claim gets its teeth, and it is more careful than most. Version metadata is assembled from git rather than from the wall clock, using git describe with tags, the commit hash from git rev-parse, and a tree state that flips to dirty when git diff reports changes.
GIT_VERSION ?= $(shell git describe --tags --always --dirty)
GIT_HASH ?= $(shell git rev-parse HEAD)
SOURCE_DATE_EPOCH ?= $(shell git log -1 --pretty=%ct)
BUILD_DATE ?= $(shell date -u -d "@$(SOURCE_DATE_EPOCH)" "$(DATE_FMT)" 2>/dev/null || date -u -r "$(SOURCE_DATE_EPOCH)" "$(DATE_FMT)" 2>/dev/null || date -u "$(DATE_FMT)")The SOURCE_DATE_EPOCH handling is the standard trick for reproducible builds, and here it falls back across GNU date, BSD date and finally plain UTC so the same Makefile works on Linux and macOS. Note that the git-derived version and the derived build date feed LDFLAGS through the release-utils version package, which is how a Go binary ends up with an embedded build timestamp that still matches between two machines.
The build is also done with ko, targeting multiple platforms at once. The ko target sets a bare build, passes --platform=all, and applies three tags at once: the image tag, the git version and the git hash. Three tags on one image means you can pull by any of them, which is what makes it practical to reference a build in a deployment manifest and still be able to trace it back to a commit.
The Go module itself requires go 1.27.0 and includes go-containerregistry, cobra, compression libraries, packageurl-go, Prometheus client, OpenTelemetry and step crypto among its dependencies.
Editorial conclusion
Adopt apko if your images are assembled from existing apk packages and you need bitwise reproducible output plus an SBOM that covers the whole filesystem, which is exactly the case for CI rebuilding images on every upstream patch. Do not adopt it if your build needs to compile, generate or download anything during the image build, because apko has no RUN equivalent and refuses to pretend otherwise. Verify first that your custom tooling is packaged as an apk, which in practice means using melange or accepting that you must build a base with apko and finish the job with a Dockerfile.
Frequently asked questions
How do I install apko?
Four routes are given: brew install apko, mise use -g apko@latest which lets you pin a version, go install chainguard.dev/apko@latest for a source install, or docker run cgr.dev/chainguard/apko version to use the published container image without installing anything.
Can apko run commands while building an image like a Dockerfile RUN step?
No, and that is by design. The README states apko does not support an equivalent of RUN statements, which is what makes its files fully declarative and its images bitwise reproducible. Bespoke tooling has to be packaged as an apk first, using the melange project.
How do I publish an image built by apko to a registry?
Use apko publish with a config file and an image reference, for example apko publish examples/alpine-base.yaml myrepo/alpine-apko:test. Building to a tarball first with apko build and loading it with docker load is the local alternative.
Official sources
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.
[](https://hysenlabs.com/projects/chainguard-dev-apko)Was this useful?