How apko Is Designed

Originally published in Japanese at https://zenn.dev/ymotongpoo/books/chainguard-image-toolchain/viewer/25-apko-concept.

apko is a tool that assembles an OCI container image purely from a combination of APK packages. It has no means of executing arbitrary commands the way Dockerfile’s RUN does — the contents of the image are determined entirely by declarations written in YAML. That constraint is the heart of apko.

Why it eliminates the imperative style

As we saw in Chapter 2, Dockerfile’s RUN is flexible, but that flexibility makes it hard to mechanically verify what happened inside the image. apko offers no way to affect the image other than installing the APK packages listed in contents.packages. Not being able to run arbitrary commands means, conversely, that you can mechanically guarantee that every file in the image traces back to one of the specified APK packages. That’s why apko’s SBOM is accurate, and it’s also why the same apko.yaml reproduces the same result.

The structure of apko.yaml

apko’s configuration file is also a single, self-contained YAML file. The main fields are:

FieldRole
contents.repositoriesThe URLs of the APK repositories to reference (Wolfi, Alpine, and so on)
contents.packagesThe list of packages to install in the image
contents.keyringThe public keys used to verify packages
entrypoint.command / cmdThe startup command, equivalent to a container’s ENTRYPOINT/CMD
accounts.users / accounts.groups / accounts.run-asDefinitions of the runtime user, letting you explicitly run as non-root
environmentEnvironment variables set in the container
archsThe target architectures to build for (x86_64, aarch64, and so on)
pathsSettings to adjust file ownership or permissions after the fact

Let’s look at a minimal example, taken from apko’s README:

contents:
  repositories:
    - https://dl-cdn.alpinelinux.org/alpine/v3.22/main
  packages:
    - alpine-base

entrypoint:
  command: /bin/sh -l

environment:
  PATH: /usr/local/sbin:/usr/local/bin:/usr/bin:/usr/sbin:/sbin:/bin

This definition, complete in just a few lines, points contents at Alpine’s official repository and the alpine-base package, and uses entrypoint.command to just start a shell. The next chapter swaps this for a Wolfi-based configuration and actually builds it.

Besides directly launching a single process, entrypoint also supports a form where you specify type: service-bundle and let the s6 supervision suite1 manage multiple processes.

The flow of image generation

An apko build proceeds by resolving dependencies among the specified packages, unpacking files from each APK as a layer, generating an SBOM, and emitting the result as an OCI image. The timestamps, ownership, and permissions of files in a layer are determined deterministically from the declared values in apko.yaml, not from the state of the execution environment. Eliminating any dependency on the build host’s clock or randomness means the same input always produces an image with identical bytes.

Multi-architecture support doesn’t rely on a cross-build mechanism like Docker Buildx; instead, it resolves package dependencies independently for each specified architecture, then bundles the results into a single tag as an OCI Image Index. If a package only exists for a particular architecture, it’s excluded from resolution for the other architectures, keeping things consistent.

Composing the SBOM

The SBOM apko generates isn’t simply a list of the installed APK packages. Beyond aggregating each package’s metadata, checksums, and license information, when it detects information bundled inside a package itself (per the SBOM composition documentation, an SPDX file under /var/lib/db/sbom/), it merges that in too, producing a more detailed, composed SBOM.

melange, which we saw in the previous chapter, embeds SBOM fragments inside built APK packages in exactly this format. In other words, apko carries forward every piece of information melange planted, without dropping any of it. apko’s policy is to record only what it can confirm — it never fills in gaps by guessing at a package’s contents.

apko as a “consumer only”

apko has no means of compiling source code. If you need a custom application, or a patched package, you need to build it as an APK package with melange (as in the previous chapters) first, and place it in a local or remote APK repository that apko’s contents.packages can reference. The division of labor — melange “builds,” apko “assembles” — comes directly out of this constraint.

The next chapter actually writes an apko.yaml and assembles an image.


  1. s6 is a lightweight set of process-supervision tools that runs as PID 1 inside a Linux container, handling the startup, monitoring, and signal handling of multiple processes. apko’s README describes it this way: “apko supports using the s6 supervision suite to run multiple processes in a container without reaping or signalling issues.” s6-overlay is a widely used distribution format for containers. ↩︎