Distroless images using melange and apko
Tags: distroless, docker, melange, apko
Published: 2023-12-22 17:16:17


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](https://www.chainguard.dev/unchained/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](https://www.redhat.com/en/blog/why-distroless-containers-arent-security-solution-you-think-they-are)

** [Understanding attacker techniques in distroless containers](https://www.chainguard.dev/unchained/understanding-attacker-techniques-for-distroless-containers) 


### Table Of Contents
  * [Sample Rust program](#rust-program)
  * [Building apks with melange](#melange)
  * [Building OCI images with apko](#apko)
  * [Scanning for vulnerabilities](#scan)
  * [Source code](#source-code)
  * [References](#references)


### Sample Rust program <a name="rust-program"></a>

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

Create a new Rust project:

```bash
cargo new random && cd random
```

Add `rand`  crate to your project:

```bash
cargo add rand
```

or manually add this to your `Cargo.toml`:

```toml
[dependencies]
rand = "0.8.5"
```

Add this code to `main.rs`:

```rust
use rand::Rng;

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

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

Run it:

```bash
cargo run
```


### Building apks with melange <a name="melange"></a>

[**melange**](https://github.com/chainguard-dev/melange) allows us to build .apk packages (compatible with [apk](https://wiki.alpinelinux.org/wiki/Package_management), 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:

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

[You can get more info about these fields here](https://github.com/chainguard-dev/melange/blob/main/docs/BUILD-FILE.md)

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:

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

Generate a temporary keypair to sign your melange packages:

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

Build an apk for your host architecture:

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

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 <a name="apko"></a>

[apko](https://github.com/chainguard-dev/apko) allows us to build [OCI container](https://opencontainers.org/) images from .apk packages.

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

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

[You can get more info about these fields here](https://github.com/chainguard-dev/apko/blob/main/docs/apko_file.md)

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

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

Run it:

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

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** <a name="scan"></a>

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

Using [Docker Scout](https://github.com/docker/scout-cli):

```bash
docker scout cves "${ARCH_REF}"
```

![Scan results using Docker Scout](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/o29p6u3wveyz5tyg2vyn.png)

Using [Trivy](https://aquasecurity.github.io/trivy):

```bash
trivy image "${ARCH_REF}"
```

![Scan results using Trivy](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vjslcpmusqt357mombu6.png)

Using [Grype](https://github.com/anchore/grype):

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

![Scan results using Grype](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/727eivj4r6hq8fuqwstb.png)


### **Source code** <a name="source-code"></a>

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


### **References** <a name="references"></a>
  * [Minimal container images: Towards a more secure future](https://www.chainguard.dev/unchained/minimal-container-images-towards-a-more-secure-future)
  * [hello-melange-apko](https://github.com/chainguard-dev/hello-melange-apko)
  * [melange docs](https://github.com/chainguard-dev/melange/tree/main/docs)
  * [apko docs](https://github.com/chainguard-dev/apko/tree/main/docs)
  * [Rust](https://www.rust-lang.org/learn/get-started)
  * [rand crate](https://crates.io/crates/rand)