# How apko Is Designed

> Source: https://www.ymotongpoo.com/books/chainguard-image-toolchain/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:

| Field | Role |
|---|---|
| `contents.repositories` | The URLs of the APK repositories to reference (Wolfi, Alpine, and so on) |
| `contents.packages` | The list of packages to install in the image |
| `contents.keyring` | The public keys used to verify packages |
| `entrypoint.command` / `cmd` | The startup command, equivalent to a container's ENTRYPOINT/CMD |
| `accounts.users` / `accounts.groups` / `accounts.run-as` | Definitions of the runtime user, letting you explicitly run as non-root |
| `environment` | Environment variables set in the container |
| `archs` | The target architectures to build for (`x86_64`, `aarch64`, and so on) |
| `paths` | Settings to adjust file ownership or permissions after the fact |

Let's look at a minimal example, taken from [apko's README](https://github.com/chainguard-dev/apko):

```yaml
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 suite[^s6] 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](https://github.com/chainguard-dev/apko/blob/main/docs/sbom-composition.md) 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.

[^s6]: [s6](https://skarnet.org/software/s6/index.html) 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](https://github.com/chainguard-dev/apko) 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](https://github.com/just-containers/s6-overlay) is a widely used distribution format for containers.

