How melange Is Designed
Originally published in Japanese at https://zenn.dev/ymotongpoo/books/chainguard-image-toolchain/viewer/15-melange-concept.
melange is a Go-based CLI tool that builds APK packages from a declarative pipeline definition. Its README frames the tool as a means to realize a “secure software factory.” The idea is that by writing each build step down as a declaration and sandboxing the execution environment, you keep how a package was built in a mechanically verifiable state.
The structure of melange.yaml
You write a melange build definition in a single YAML file (conventionally named melange.yaml, though any filename works). The main top-level fields are:
| Field | Role |
|---|---|
package | Metadata: package name, version, epoch, description, license, runtime dependencies, and so on |
environment | Defines the build environment. contents.repositories and contents.packages specify the APK repository and packages used at build time |
pipeline | The sequence of build steps, made up of built-in uses actions or raw shell commands written under runs |
subpackages | Derived packages built separately from the main one (such as -doc or -dev). Each can have its own pipeline and dependencies |
test | A pipeline for verifying the built package |
Each pipeline step either calls a pre-built action, as in uses: fetch, or writes an arbitrary shell command under runs: |. The built-in actions themselves are defined one action per YAML file in the pkg/build/pipelines directory. They include fetch for retrieving source, build-system wrappers like autoconf/configure and autoconf/make, go/build for Go, patch for applying patches, and strip for removing debug symbols. Using built-in actions lets you share the same kind of build work across multiple package definitions.
Variable expansion uses notation like ${{package.version}}, ${{targets.destdir}}, and ${{build.arch}}, letting you reference the version number or build output directory directly inside the pipeline.
A table only gets you so far, so let’s look at the skeleton of the quickstart example in melange’s README:
package:
name: hello
version: 2.12
epoch: 0
description: "the GNU hello world program"
environment:
contents:
repositories:
- https://dl-cdn.alpinelinux.org/alpine/edge/main
packages:
- alpine-baselayout-data
- busybox
- build-base
pipeline:
- uses: fetch
with:
uri: https://ftp.gnu.org/gnu/hello/hello-${{package.version}}.tar.gz
expected-sha256: cf04af86dc085268c5f4470fbae49b18afbc221b78096aab842d934a76bad0ab
- uses: autoconf/configure
- uses: autoconf/make
- uses: autoconf/make-install
- uses: strip
package carries the name and version, environment.contents lists the Alpine repository and packages used as the build environment, and pipeline lays out the steps from fetching the source to installing it. The next chapter’s hands-on exercise actually runs this example, but swaps the build environment from Alpine to a Wolfi repository. The next chapter explains why.
Sandboxing the build
Every time melange runs a pipeline, it prepares a clean APK root environment and performs the build inside it. You can choose the execution environment (the runner) with the --runner flag — bubblewrap, docker, or qemu — and the default varies by platform. bubblewrap is a lightweight sandbox using Linux’s namespace isolation feature; running it inside a container requires the --privileged flag, due to the Linux capabilities it needs.
Multi-architecture support is achieved through QEMU’s user-mode emulation. Without setting up a separate cross-compilation environment, you can run builds for different architectures — x86_64, arm64, ppc64le, and others — on the same host.
Recording the SBOM and provenance
melange generates an SPDX-format SBOM on every build and embeds it inside the resulting package. Because it’s generated from inside the build process, rather than inferred later by scanning the filesystem (the problem covered in Chapter 2), the record is based on what the pipeline actually fetched and actually installed.
Passing the --generate-provenance flag additionally outputs SLSA-format provenance information as .attest.tar.gz. The provenance records which build definition, in which environment, and when the build happened.
Signing as a prerequisite
An APK built with melange isn’t treated as trustworthy on its own. Signing it with a key pair generated by melange keygen makes it possible to verify that a specific build entity produced the package. This signing mechanism is indispensable for setting up a public APK repository like Wolfi’s. The next chapter’s hands-on exercise walks through actually generating a key and signing a package.