# Building an APK Package with melange

> Source: https://www.ymotongpoo.com/books/chainguard-image-toolchain/20-melange-handson/


Let's confirm the mechanism from the previous chapter by actually running commands. melange is a single Go binary, but since building uses Linux namespace isolation, the easiest approach is to use the `cgr.dev/chainguard/melange` image, run on Docker. The steps that follow track the quickstart in [melange's README](https://github.com/chainguard-dev/melange).

## Preparing a signing key

APKs built with melange must be signed. First, generate a key pair in your working directory:

```shell
docker run --rm -v "${PWD}":/work cgr.dev/chainguard/melange keygen
```

This creates a private key, `melange.rsa`, and a public key, `melange.rsa.pub`, in the current directory. You'll reuse this key for every build that follows.

## Preparing a build definition

As Chapter 4 showed, the quickstart example in melange's [README](https://github.com/chainguard-dev/melange) uses Alpine Linux's official repository as the build environment. In this book, though, we'll align the build environment with Wolfi's own repository, since Chapter 7 embeds the APK we build here into a Wolfi-based apko image. As confirmed in Chapter 3, Alpine Linux uses `musl` as its standard C library while Wolfi uses `glibc`, so a binary built in an Alpine environment can't just be dropped into a Wolfi image as-is. Rewriting the README's example for Wolfi gives you this:

```yaml
package:
  name: hello
  version: 2.12
  epoch: 0
  description: "the GNU hello world program"
  copyright:
    - license: GPL-3.0-or-later

environment:
  contents:
    keyring:
      - https://packages.wolfi.dev/os/wolfi-signing.rsa.pub
    repositories:
      - https://packages.wolfi.dev/os
    packages:
      - wolfi-base
      - build-base
      - ca-certificates-bundle

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
```

`environment.contents` assembles the build environment itself from Wolfi packages — the minimal `wolfi-base` from Chapter 3, `build-base` for the full build toolchain, and `ca-certificates-bundle` for HTTPS. `pipeline` then runs through fetching the source, building, installing, and stripping debug symbols, in order. `autoconf/configure` and `autoconf/make` are built-in actions that package up the equivalent of `./configure && make`. `ca-certificates-bundle` is required — without it, the `fetch` action fails to verify the TLS certificate when talking to `https://ftp.gnu.org`.

## Running the build

Build using the key and definition file you've prepared:

```shell
docker run --privileged --rm -v "${PWD}":/work \
  cgr.dev/chainguard/melange build hello.yaml \
  --arch x86_64 --signing-key melange.rsa
```

`--privileged` is needed for melange to set up its sandbox (`bubblewrap` by default) inside the container. On success, the build produces a signed `hello-2.12-r0.apk`, along with `APKINDEX.tar.gz`, the repository index file, under `packages/x86_64/` in the current directory.

## Inspecting the output

The generated `.apk` is, underneath, a tar archive containing signature information, metadata, and the actual set of files. You can inspect its contents with the `tar` command:

```shell
tar tzf packages/x86_64/hello-2.12-r0.apk
```

Alongside the executable, the package contains an SPDX-format SBOM file under `var/lib/db/sbom/`. As Chapter 4 mentioned, this SBOM is generated from inside the build process. Here's an excerpt of the structure, taken from an [actual SBOM](https://github.com/chainguard-dev/melange/blob/main/pkg/build/testdata/goldenfiles/sboms/sed-4.9-r8.spdx.json) (for the `sed` package) included in melange's test data:

```json
{
  "SPDXID": "SPDXRef-DOCUMENT",
  "name": "apk-sed-4.9-r8",
  "spdxVersion": "SPDX-2.3",
  "creationInfo": {
    "creators": ["Tool: melange (devel)", "Organization: Chainguard, Inc"]
  },
  "documentDescribes": ["SPDXRef-Package-apk-sed-4.9-r8"],
  "packages": [
    { "SPDXID": "SPDXRef-OperatingSystem", "name": "wolfi", "primaryPackagePurpose": "OPERATING-SYSTEM" },
    {
      "SPDXID": "SPDXRef-Package-apk-sed-4.9-r8",
      "name": "sed",
      "versionInfo": "4.9-r8",
      "licenseDeclared": "GPL-3.0-or-later",
      "primaryPackagePurpose": "APPLICATION"
    },
    { "SPDXID": "SPDXRef-Package-Melange-testdata-buildC95configs-sed.yaml-c0ffee", "primaryPackagePurpose": "INSTALL" },
    { "SPDXID": "SPDXRef-Package-Source-git.savannah.gnu.org...", "primaryPackagePurpose": "SOURCE" }
  ]
}
```

Notice that the `packages` array is made up of four kinds of entries: the package itself (`APPLICATION`), the build definition that produced it (`INSTALL`), the source code it was fetched from (`SOURCE`), and its OS context (`OPERATING-SYSTEM`). Because this records exactly what was fetched and installed from inside the build process, it's more accurate than the after-the-fact filesystem-scanning approach.

Adding the `--generate-provenance` flag outputs an SLSA-format provenance file alongside the build. If you're building automatically in a CI environment, keeping this provenance information around lets you trace, later on, exactly which inputs a given package was built from.

## Subpackages and tests

Adding `subpackages` to `melange.yaml` lets you produce a separate APK from the same build as the main package. For example, to split out a docs-only `hello-doc`, you'd write:

```yaml
subpackages:
  - name: hello-doc
    pipeline:
      - uses: split/manpages
```

Writing a verification pipeline in the `test` field also lets you validate the build's output with the `melange test` command. The (excerpted) test definition for the `crane` package in melange's examples is a [clear example](https://github.com/chainguard-dev/melange/blob/main/examples/test-xcover.yaml):

```yaml
test:
  environment:
    contents:
      packages:
        - jq
  pipeline:
    - name: Verify Crane installation
      runs: |
        crane version || exit 1
        crane --help
    - name: Fetch and verify manifest
      runs: |
        crane manifest chainguard/static | jq '.schemaVersion' | grep '2' || exit 1
```

This verifies not just that the built `crane` command actually runs, but that it can talk to a registry and correctly fetch a manifest. This pattern is used widely across Wolfi's repository, a large collection of package definitions.

In the next chapter, we'll combine the `hello` APK built here with the APKs Wolfi publishes, and actually assemble a container image.

