In my day job, we've been using Google's Distroless images for some time. The benefits of this are well known: smaller image and attack surface. However, what we didn't expect is to still to have to deal with a significant amount of toil dealing with vulnerability triage (see Snyk output below for
distroless/base). It could be argued our SDLC is a little, clunky and we're not quite ready for
scratch (Graal, Golang, etc.,) images yet so what can we do in the interim? 🤔
That lead me to think about a distroless Alpine Linux image. A quick google shows I'm not the only one as a recent Medium article attests. But as we're mostly dealing with Java to get a working image by pulling library-by-library is somewhat of a faff. Fortunately, APKO to the rescue.
APKO is a tool to create lean images using Alpine with only the stuff we need.
At time of writing an Alpine 3.15 image was 5.57MB. Pretty small but still includes some things we don't need, such as
sh shell. So with the help of the examples from APKO I came up with:
contents: repositories: - https://dl-cdn.alpinelinux.org/alpine/v3.15/main packages: - musl - zlib accounts: groups: - groupname: nonroot gid: 10000 users: - username: nonroot uid: 10000 run-as: nonroot os-release: version-id: '3.15' # defaults to 3.16/edge
A few things to note here:
3.15repos, rather than
edgeused in the examples,
- Adding a few packages:
musl: Needed for pretty much everything,
zlib: Required for JRE 17, crashes without it.
- Using non-root user.
PS ...> docker run --rm ` -v $pwd/:/app:rw ` -w /app ghcr.io/chainguard-dev/apko:v0.3.3 ` build rootless.yaml alpine:3.15-apko apko.tar 2022/05/10 09:21:44 loading config file: rootless.yaml 2022/05/10 09:21:45 apko (x86_64): building image 'alpine:3.15-apko' ... PS ...> docker load -i .\apko.tar Loaded image: alpine:3.15-apko
The result is a very small image:
REPOSITORY TAG IMAGE ID CREATED SIZE alpine 3.15 0ac33e5f5afa 4 weeks ago 5.57MB alpine 3.15-apko 5a3ea808f8ed 52 years ago 709kB
That's nearly 1/8th of the size (12.7%) and a grand total of just 2 packages (down from 14):
PS ...> docker scan alpine:3.15-apko Testing alpine:3.15-apko... Package manager: apk Project name: docker-image|alpine Docker image: alpine:3.15-apko Platform: linux/amd64 ✔ Tested 2 dependencies for known vulnerabilities, no vulnerable paths found.
That's much smaller than roughly equivalent
REPOSITORY TAG IMAGE ID CREATED SIZE gcr.io/distroless/base nonroot 555ca12a9222 52 years ago 20.3MB
That's over 28x larger and that does have potential issues:
PS ...> docker scan gcr.io/distroless/base:nonroot Testing gcr.io/distroless/base:nonroot... ... Package manager: deb Project name: docker-image|gcr.io/distroless/base Docker image: gcr.io/distroless/base:nonroot Platform: linux/amd64 Tested 6 dependencies for known vulnerabilities, found 11 vulnerabilities.
Ok, so I don't want to use a JLink minimal JRE, what the difference between a Java 17 JRE:
REPOSITORY TAG IMAGE ID CREATED SIZE gcr.io/distroless/java17-debian11 nonroot 678ed8ce3ba5 52 years ago 231MB alpine jre17-apko d2302101850c 52 years ago 202MB
29MB less, that's what!
So, we can have a distroless Alpine image, that's even smaller than Google's with a smaller attack surface. 🔥
The maintainers of APKO highlight it's not released yet and liable to change, but it's a great start.