Getting Started with melange

melange is a declarative apk builder

melange is an apk builder tool that uses declarative pipelines to create apk packages. From a single YAML file, users are able to generate multi-architecture apks that can be injected directly into apko builds.

Understanding melange can help you better understand the Wolfi operating system and how Chainguard Images are made to be minimal and secure, but it is not necessary to have a background in melange in order to use Chainguard Images.

In this guide, you’ll learn how to build a software package with melange. To demonstrate the versatile combination of melange and apko builds, we’ll package a small command-line PHP script and build a minimalist container image based on Wolfi with the generated apk. All files used in this demo are open source and available at the melange-php-demos repository.

Requirements

Our guide is compatible with operating systems that support Docker and shared volumes. Please follow the appropriate Docker installation instructions for your operating system.

You won’t need PHP or Composer installed on your system, since we’ll be using Docker to build the demo app.

Note for Linux Users

In order to be able to build apks for multiple architectures using Docker, you may need to register additional QEMU headers within your kernel. This is done automatically for Docker Desktop users, so if you are on macOS you don’t need to run this additional step.

Run the following command to register the necessary handlers within your kernel, using the multiarch/qemu-user-static image.

docker run --rm --privileged multiarch/qemu-user-static --reset -p yes

You should now be able to build apks for all architectures supported by melange.

1 — Downloading the melange Image

The fastest way to get melange up and running on your system is by using the official melange image with Docker. Start by pulling the melange image into your local system:

docker pull cgr.dev/chainguard/melange:latest

This will download the latest version of the melange image, which is rebuilt every night for extra freshness.

Check that you’re able to run melange with docker run.

docker run --rm cgr.dev/chainguard/melange version

You should get output similar to the following:

  __  __   _____   _          _      _   _    ____   _____
 |  \/  | | ____| | |        / \    | \ | |  / ___| | ____|
 | |\/| | |  _|   | |       / _ \   |  \| | | |  _  |  _|
 | |  | | | |___  | |___   / ___ \  | |\  | | |_| | | |___
 |_|  |_| |_____| |_____| /_/   \_\ |_| \_|  \____| |_____|
melange

GitVersion:    v0.11.1
GitCommit:     a52edcc075ebf1dc89aea87893e3821944171ee3
GitTreeState:  clean
BuildDate:     '2024-07-19T16:04:17Z'
GoVersion:     go1.22.5
Compiler:      gc
Platform:      linux/amd64

With melange installed, you’re ready to proceed.

2 — Cloning the Demo Repository

To demonstrate melange’s features with a minimalist application that has real-world functionality, our demo consists of a PHP command line app that queries the Slip advice API and outputs a random piece of advice. The app is a single-file script built with Minicli.

Start by cloning the demo repository to your local machine and navigating to the melange-php-demos/hello-minicli directory:

git clone git@github.com:chainguard-dev/melange-php-demos.git
cd melange-php-demos/hello-minicli

Now you can run the application to make sure it’s functional. You can do that using Docker and Chainguard’s PHP image:

docker run --rm -it -v "${PWD}":/app cgr.dev/chainguard/php /app/minicli advice

You should get a random piece of advice such as:

Gratitude is said to be the secret to happiness.

With the application ready, you can start building your package.

3 — The melange YAML File

The melange.yaml file is where you declare the details and specifications of your apk package. For code that generates self-contained binaries, this is typically where you’ll build your application artifacts with compiler tools. In the case of interpreted languages, you’ll likely build your application by downloading vendor dependencies, setting up relevant paths, and setting the environment up for production.

The melange specification file contains three main sections:

  • package: defines package specs, such as name, license, and runtime dependencies. Runtime dependencies will be brought into the system automatically as dependencies when the apk is installed.
  • environment: defines how the environment should be prepared for the build, including required packages and their source repositories. Anything that is only required at build time goes here, and shouldn’t be part of the runtime dependencies.
  • pipeline: defines the build pipeline for this package.

One of the best advantages of using melange is to be able to control all steps of your build pipeline, and include only what’s necessary. This way, you’ll be able to build smaller and more secure container images by removing unnecessary dependencies.

This is what the melange.yaml included in our demo looks like, for your reference:

package:
  name: hello-minicli
  version: 0.1.0
  description: Minicli melange demo
  target-architecture:
    - all
  copyright:
    - license: MIT
  dependencies:
    runtime:
      - php
      - php-curl

environment:
  contents:
    keyring:
      - https://packages.wolfi.dev/os/wolfi-signing.rsa.pub
      - ./melange.rsa.pub
    repositories:
      - https://packages.wolfi.dev/os
    packages:
      - ca-certificates-bundle
      - busybox
      - curl
      - git
      - php
      - php-phar
      - php-iconv
      - php-openssl
      - php-curl
      - composer

pipeline:
  - name: Build Minicli application
    runs: |
      MINICLI_HOME="${{targets.destdir}}/usr/share/minicli"
      EXEC_DIR="${{targets.destdir}}/usr/bin"
      mkdir -p "${MINICLI_HOME}" "${EXEC_DIR}"
      cp ./composer.json "${MINICLI_HOME}"
      /usr/bin/composer install -d "${MINICLI_HOME}" --no-dev
      cp ./minicli "${EXEC_DIR}"
      chmod +x "${EXEC_DIR}/minicli"      

Our build pipeline will set up two distinct directories, separating the application dependencies from its executable entry point. The executable minicli script will be copied into /usr/bin, while the vendor files will be located at /usr/share/minicli.

4 — Building the minicli apk with melange

Before building the package, you’ll need to create a temporary keypair to sign it. You can use the following command for that:

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

This will generate a melange.rsa and melange.rsa.pub files in the current directory.

2024/08/01 16:55:31 INFO generating keypair with a 4096 bit prime, please wait...
2024/08/01 16:55:33 INFO wrote private key to melange.rsa
2024/08/01 16:55:33 INFO wrote public key to melange.rsa.pub

Next, build the apk defined in the melange.yaml file with the following command:

docker run --privileged --rm -v "${PWD}":/work \
  cgr.dev/chainguard/melange build melange.yaml \
  --arch amd64,aarch64 \
  --signing-key melange.rsa

This will set up a volume sharing your current folder with the location /work inside the container. We’ll build packages for amd64 and aarch64 platforms and sign them using the melange.rsa key created in the previous command.

When the build is finished, you should be able to find a packages folder containing the generated apks (and associated apk index files):

packages
├── aarch64
│   ├── APKINDEX.json
│   ├── APKINDEX.tar.gz
│   └── hello-minicli-0.1.0-r0.apk
└── x86_64
    ├── APKINDEX.json
    ├── APKINDEX.tar.gz
    └── hello-minicli-0.1.0-r0.apk

3 directories, 6 files

You have successfully built a multi-architecture software package with melange!

5 — Building a Container Image with apko

With the apk packages and apk index in place, you can now build a container image and have your apk(s) installed within it.

The following apko.yaml file will create a container image tailored to the application we built in the previous steps. Because we defined the PHP dependencies as runtime dependencies within the apk, you don’t need to require these packages again here. The container entrypoint command will be set to /usr/bin/minicli, where the application executable is located.

One important thing to note is how we reference the hello-minicli apk as a local package within the repositories section of the YAML file. The @local notation tells apko to search for apks in the specified directory, in this case /work/packages.

This is what the apko.yaml file included in our demo looks like, for your reference:

contents:
  keyring:
    - https://packages.wolfi.dev/os/wolfi-signing.rsa.pub
    - ./melange.rsa.pub
  repositories:
    - https://packages.wolfi.dev/os
    - '@local /work/packages'
  packages:
    - wolfi-base
    - ca-certificates-bundle
    - hello-minicli@local
accounts:
  groups:
    - groupname: nonroot
      gid: 65532
  users:
    - username: nonroot
      uid: 65532
  run-as: 65532
entrypoint:
  command: /usr/bin/minicli advice

The following command will set up a volume sharing your current folder with the location /work in the apko container, running the apko build command to generate an image based on your apko.yaml definition file.

docker run --rm --workdir /work -v ${PWD}:/work cgr.dev/chainguard/apko \
  build apko.yaml hello-minicli:test hello-minicli.tar --arch host

This will build an OCI image based on your host system’s architecture (specified by the --arch host flag). If you receive warnings at this point, those are likely related to the types of SBOMs being uploaded and can be safely ignored.

The command will generate a few new files in the app’s directory:

  • hello-minicli.tar — the packaged OCI image that can be imported with a docker load command
  • sbom-%host-architecture%.spdx.json — an SBOM file for your host architecture in spdx-json format

Next, load your image within Docker:

docker load < hello-minicli.tar
7cbaefdf1c30: Loading layer   13.7MB/13.7MB
Loaded image: hello-minicli:test-%host-architecture%

Note that the %host-architecture% will vary, and there may be multiple images loaded into your Docker daemon. Be sure to edit the variable in the following docker run command to match your target architecture.

Now you can run your Minicli program with:

docker run --rm hello-minicli:test-%host-architecture%

The demo should output an advice slip such as:

Only those who attempt the impossible can achieve the absurd.

You have successfully built a minimalist container image with your apk package installed on it. This image is fully OCI compatible and can be signed with Cosign for provenance attestation.

Conclusion

In this guide, we packaged a PHP command-line app with melange. We also built a container image to install and run our custom apk, using the apko tool. For more information about apko, check our Getting Started with apko guide.

The demo files are available at the melange-php-demos repository, in the hello-minicli subfolder. For additional information on how to debug your builds and other features, check the melange and apko repositories on GitHub.

Last updated: 2024-08-01 15:21