Distroless images using melange and apko
Henrique Santos
Posted on December 22, 2023
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.
** ”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
- Building apks with melange
- Building OCI images with apko
- Scanning for vulnerabilities
- Source code
- References
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
Add rand
crate to your project:
cargo add rand
or manually add this to your Cargo.toml
:
[dependencies]
rand = "0.8.5"
Add this code to main.rs
:
use rand::Rng;
fn main() {
let mut rng = rand::thread_rng();
let number: u8 = rng.gen();
println!("{}", number);
}
Run it:
cargo run
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"
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
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
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
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
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
Run it:
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
Let’s scan our image and see if we detect any vulnerabilities.
Using Docker Scout:
docker scout cves "${ARCH_REF}"
Using Trivy:
trivy image "${ARCH_REF}"
Using Grype:
grype "${ARCH_REF}" --scope all-layers
Source code
Get the code for this lab here: https://github.com/henriquencmt/distroless-melange-apko
References
Posted on December 22, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.