Distroless Alpine
Tags: docker, security, kubernetes, java
Published: 2022-05-10 19:53:39

In my day job, we've been using [Google's Distroless](https://github.com/GoogleContainerTools/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? :thinking:

That lead me to think about a distroless [Alpine Linux](https://www.alpinelinux.org/) image. A quick google shows I'm not the only one as a recent [Medium article](https://medium.com/inside-sumup/stop-using-alpine-docker-images-fbf122c63010) 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](https://github.com/chainguard-dev/apko) to the rescue.

APKO is a tool to create lean images using Alpine with only the stuff we need. 

## Demo

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:

```yaml
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:
* Using `3.15` repos, rather than `edge` used in the examples,
* Adding a few packages:
 * `musl`: Needed for pretty much everything,
 * `zlib`: Required for JRE 17, crashes without it.
* Using [non-root](https://docs.docker.com/engine/security/rootless/) 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):

```powershell
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 `distroless/base`:

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

```powershell
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.
```

I've trialled my new image with an [existing project](https://github.com/dansiviter/helidon-gcp/tree/apko) via JLink that's heavy on Netty and gRPC the image works great (with a small tweak to exclude `grpc-netty-shaded` due to [grpc-java#9083](https://github.com/grpc/grpc-java/issues/9083)).

## What about Java?

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!

## Conclusion

So, we can have a distroless Alpine image, that's even smaller than Google's with a smaller attack surface. :fire:

The maintainers of APKO highlight it's not released yet and liable to change, but it's a great start.