DEV Community

Cover image for Distroless images using melange and apko
Henrique Santos
Henrique Santos

Posted on

Distroless images using melange and apko

TL;DR - Beyond size, which can save time and resources, distroless images* have their complexity and attack surface reduced**. In this post, we are going to use melange and apko, from Chainguard, to build an apk package with a small Rust program, build an OCI image from it and then load it with Docker.

* “Distroless” doesn’t mean that the image has no distro at all. The idea is to work with minimal images, so we should bring into it only the essentials for our apps to run, leaving out shells, package managers, etc.

** Image sizes miss the point

** ”standardization and quality of the software in your direct execution path lowers your attack surface more than distroless does” - Why distroless containers aren't the security solution you think they are

** Understanding attacker techniques in distroless containers

Table Of Contents

Sample Rust program

We are going to build a small Rust program that prints a random value every time we run it.

Create a new Rust project:



cargo new random && cd random


Enter fullscreen mode Exit fullscreen mode

Add rand crate to your project:



cargo add rand


Enter fullscreen mode Exit fullscreen mode

or manually add this to your Cargo.toml:



[dependencies]
rand = "0.8.5"


Enter fullscreen mode Exit fullscreen mode

Add this code to main.rs:



use rand::Rng;

fn main() {
    let mut rng = rand::thread_rng();

    let number: u8 = rng.gen();
    println!("{}", number);
}


Enter fullscreen mode Exit fullscreen mode

Run it:



cargo run


Enter fullscreen mode Exit fullscreen mode

Building apks with melange

melange allows us to build .apk packages (compatible with apk, the package manager used by Alpine Linux distro) using declarative YAML pipelines.

First, create a file melange.yaml and add the following instructions to it:



package:
  name: random
  version: 0.1.0
  description: random number generator
  target-architecture:
    - all
  copyright:
    - license: Apache-2.0
      paths:
        - "*"

environment:
  contents:
    repositories:
      - https://dl-cdn.alpinelinux.org/alpine/edge/main
      - https://dl-cdn.alpinelinux.org/alpine/edge/community
    packages:
      - alpine-baselayout-data
      - ca-certificates-bundle
      - busybox
      - cargo

pipeline:
  - name: Build Rust application
    runs: |
      TARGETDIR="$(mktemp -d)"
      cargo build --release --target-dir "${TARGETDIR}"
      mkdir -p "${{targets.destdir}}/usr/bin"
      mv "${TARGETDIR}/release/random" "${{targets.destdir}}/usr/bin"


Enter fullscreen mode Exit fullscreen mode

You can get more info about these fields here

We are going to use two Docker images to generate a temporary keypair and to build the apk package, so we have to pull them:



docker pull ghcr.io/wolfi-dev/sdk
docker pull cgr.dev/chainguard/sdk


Enter fullscreen mode Exit fullscreen mode

Generate a temporary keypair to sign your melange packages:



docker run --rm -v "${PWD}":/work --entrypoint=melange --workdir=/work ghcr.io/wolfi-dev/sdk keygen


Enter fullscreen mode Exit fullscreen mode

Build an apk for your host architecture:



docker run --rm --privileged -v "${PWD}":/work  \
    --entrypoint=melange --workdir=/work \
    cgr.dev/chainguard/sdk build melange.yaml \
    --arch host \
    --signing-key melange.rsa


Enter fullscreen mode Exit fullscreen mode

A folder named packages should be created with the generated apks.

Now, we can to build an image and install our apk package on it. We’ll do this with apko.

Building OCI images with apko

apko allows us to build OCI container images from .apk packages.

Create a file apko.yaml and add the following content to it:



contents:
  repositories:
    - https://dl-cdn.alpinelinux.org/alpine/edge/main
    - /work/packages
  packages:
    - alpine-baselayout-data
    - random
accounts:
  groups:
    - groupname: nonroot
      gid: 65532
  users:
    - username: nonroot
      uid: 65532
  run-as: 65532
entrypoint:
  command: /usr/bin/random


Enter fullscreen mode Exit fullscreen mode

You can get more info about these fields here

Build the image (we are using ghcr.io/wolfi-dev/sdk here):



docker run --rm -v "${PWD}":/work \
    --entrypoint=apko --workdir=/work ghcr.io/wolfi-dev/sdk build --debug apko.yaml \
     distroless/random random.tar -k melange.rsa.pub \
    --arch host


Enter fullscreen mode Exit fullscreen mode

Run it:



ARCH_REF="$(docker load < random.tar | grep "Loaded image" | sed 's/^Loaded image: //' | head -1)"
docker run "${ARCH_REF}"


Enter fullscreen mode Exit fullscreen mode

You can call echo $ARCH_REF or docker images to get your image repository and tag. It should be like this: distroless/random:latest-<host-arch-here>.

Scanning for vulnerabilities

Let’s scan our image and see if we detect any vulnerabilities.

Using Docker Scout:



docker scout cves "${ARCH_REF}"


Enter fullscreen mode Exit fullscreen mode

Scan results using Docker Scout

Using Trivy:



trivy image "${ARCH_REF}"


Enter fullscreen mode Exit fullscreen mode

Scan results using Trivy

Using Grype:



grype "${ARCH_REF}" --scope all-layers


Enter fullscreen mode Exit fullscreen mode

Scan results using Grype

Source code

Get the code for this lab here: https://github.com/henriquencmt/distroless-melange-apko

References

Top comments (0)