As a followup from the discussion on software supply chain attacks, we want to talk about some new base container images available for use from Lemuridae Labs. With this, we will talk about how we are managing the vulnerability footprint, how we leverage secure base container distributions like Wolfi (from Chainguard), and how we track the provenance of the containers through signing.
When distributing a container-based software application, you need a foundation on which to build. Much like a house has a foundation, a container needs a similar starting point. A base image can be sparse and minimal, or it can have many tools and utilities needed by an application.
Many operating systems, especially Linux distributions, have a container base image that provides many of the standard tools and utilities in that system. Software package managers, data processing tools, shells, and other functions may be available in the base image. What is specifically in the image depends on the distribution and the software packaging decisions made.
One challenge when working with images is to minimize the base, and this is a balance between removing too much and making it unusable for most users, and having too much and increasing the size and potential vulnerability risks. The more software in the container image, the more potential vulnerabilities that may exist. Even if your application doesn't use some of the software and tools resident on the image, there is potential risk and wasted space.
Chainguard is a software company that creates and maintains secured container images for a wide variety of software packages. As a company they maintain many open source projects and components, and provide and sell access to secured containers as well. As part of this they provide build provenance to show the components and build process, and utilize container signing to demonstrate the integrity of the image.
A project maintained by Chainguard is Wolfi, which is intended to be a secure minimal container base image. To quote from the project Github page:
Wolfi is a Linux undistro designed from the ground up to support newer computing paradigms such as containers. Although Wolfi has a few similar design principles as Alpine (such as using apk), it is a different distribution that is focused on supply chain security. Unlike Alpine, Wolfi does not currently build its own Linux kernel, instead relying on the host environment (e.g. a container runtime) to provide one.
For our purposes, Wolfi is a secure foundation on which to build. Taking a look at the container (cgr.dev/chainguard/wolfi-base:latest) it reports a size of 14.3MB, and doing a trivy vulnerability scan reports zero vulnerabilities. Additional information on Wolfi can be found at:
https://images.chainguard.dev/directory/image/wolfi-base/overview
We start by creating a Dockerfile to add in some basic software, as well as to do a quick update in case anything has recently changed. As Wolfi is constantly updated, there should be few, if any, updates, however it never hurts to try:
FROM cgr.dev/chainguard/wolfi-base:latest
RUN set -eux && \
apk update && \
apk add --no-cache \
openjdk-24-jre \
fontconfig ttf-dejavu \
ca-certificates \
tzdata && \
rm -rf /var/cache/apk/*
RUN adduser -D appuser
ENV JAVA_HOME=/usr/lib/jvm/java-24-openjdk
ENV PATH="$JAVA_HOME/bin:${PATH}"
USER appuser
WORKDIR /app
This Dockerfile is quite simple, starting with the Wolfi base it updates, adds in OpenJDK Java 24 (JRE), some fonts, certificate authority information, and timezone data. This is opinionated, as not all people will need all components, however this makes for a good general purpose Java baseline container.
A few notes, it drops the root access in the container build process, changing to the appuser that it created in the build process, and also sets the base working directory to /app.
What are the results? The container as built jumped in size up to 255MB, but as this contains the full Java runtime, supporting libraries, and other ancillary artifacts, that's not too bad.
So adding that Java runtime increased the size, but what about the security impacts? A key focus of doing this build process is securing the foundation for our applications. Lets do a quick followup scan of the container to see how it reports:
A new trivy scan of lemuridaelabs/openjdk-java24-jre:latest shows zero vulnerabilities.
And to verify the image build:
docker run --rm lemuridaelabs/openjdk-java24-jre:latest java -version
openjdk version "24.0.2" 2025-07-15
OpenJDK Runtime Environment (build 24.0.2+-wolfi-r5)
OpenJDK 64-Bit Server VM (build 24.0.2+-wolfi-r5, mixed mode, sharing)
So we have a new Java base container available for our Java applications, we have a current Java 24 JRE available, an we have no vulnerabilities out of the box.
What if we compare to another distribution, eclipse-temurin:24.0.2_12-jre-ubi9-minimal. This image clocks in at 391MB, or 136MB larger, and certainly has a variety of other software packages available. Depending on your needs, you might need some components of the distribution, however lets check the JRE runtime:
docker run --rm eclipse-temurin:24.0.2_12-jre-ubi9-minimal java -version
openjdk version "24.0.2" 2025-07-15
OpenJDK Runtime Environment Temurin-24.0.2+12 (build 24.0.2+12)
OpenJDK 64-Bit Server VM Temurin-24.0.2+12 (build 24.0.2+12, mixed mode, sharing)
So we have reasonably equivalent builds of Java, and a sizable space difference.. but let's consider the security. We are talking about secure base container images in this article, and we want to compare vulnerabilities and risks.
Running a trivy scan returns:
eclipse-temurin:24.0.2_12-jre-ubi9-minimal (redhat 9.6)
=======================================================
Total: 104 (UNKNOWN: 0, LOW: 76, MEDIUM: 26, HIGH: 2, CRITICAL: 0)
So, we have 104 vulnerabilties found, with 76 being considered low, 26 are medium, and 2 are high, with zero critical. This is a sizable jump over the zero from our previous image.
To be clear, there may be compatibility considerations or a wide range of other reasons that you may want to use one of the other base images available. If this is your need, then this may be an acceptable baseline and security posture. However from a software supply chain perspective, we want to minimize the software contained in an application distribution, reduce the overall SBOM, and to better manage vulnerabilities and risk.
A container image is verified structurally to ensure that the image integirty is around, however this doesn't provide proof of the origin of the container, and to verify that it has not been tampered with through various software distribution channels. To support this type of validation, we can use cosign, a tool that supports image signing and verification.
Looking Chainguard containers, we can investigate the Wolfi base image with the following, somewhat complex, command:
cosign verify \
--certificate-oidc-issuer=https://token.actions.githubusercontent.com \
--certificate-identity=https://github.com/chainguard-images/images/.github/workflows/release.yaml@refs/heads/main \
cgr.dev/chainguard/wolfi-base:latest
This command will check the base Wolfi container against the source to see if it is validated. When run, we have the following results:
Verification for cgr.dev/chainguard/wolfi-base:latest --
The following checks were performed on each of these signatures:
- The cosign claims were validated
- Existence of the claims in the transparency log was verified offline
- The code-signing certificate was verified using trusted certificate authority certificates
This is great, and shows us that we have an image that has not been tampered with, and should be considered safe. This is a secure foundation to build on.
Bringing this together, we have automatic builds produced nightly by Lemuridae Labs and published to Docker hub performing this build, packaging, and signing process.
Each night Lemuridae Labs builds multiple Java 21, 23, and 24 (JRE and JDK) containers from a secure and up to date Wolfi foundation, signs these images, and publishes them for use.
You can check the image integrity and provenance with your own cosign run:
cosign verify lemuridaelabs/openjdk-java21-jre --certificate-identity-regexp 'https://gitlab.com/lemuridae-labs/base-containers' --certificate-oidc-issuer 'https://gitlab.com'
And when run on a Java 21 container image returns:
Verification for index.docker.io/lemuridaelabs/openjdk-java21-jre:latest --
The following checks were performed on each of these signatures:
- The cosign claims were validated
- Existence of the claims in the transparency log was verified offline
- The code-signing certificate was verified using trusted certificate authority certificates
And again, running trivy shows zero vulnerabilities in the base container images.
You are welcome to use these, and we love feedback! If you have thoughts, ideas, and enhancements let us know and we can see how that could benefit any image users.
https://hub.docker.com/u/lemuridaelabs
The screen below shows the Java images available, enjoy!