Open Source

How to use Dockerfiles with wolfi-base images

Adrian Mouat, Staff DevRel Engineer
September 14, 2023

At Chainguard, we have developed a suite of tooling to help us build hardened, low-vulnerability Images on top of the Wolfi Linux (un)distribution using apko and melange. But you don't need to use these tools to get started with Wolfi and begin reaping the benefits of minimal, low-vulnerability images. At the outset, it makes sense to keep using the tools you're used to–you can later look at adopting different tools like apko and melange when you want to benefit from some of the advantages they bring.

To help users get started, I've written this short guide for how to use wolfi-base with Docker tooling. We'll look at using Chainguard base images, including static (an equivalent to Google Distroless) and wolfi-base (a minimal image that includes a shell and package manager).

Images and static binaries

The most common place to start with Chainguard Images is with the static base images, which are designed for use with Docker multi-stage builds and are a direct replacement for the Google Distroless images. These are great when you can produce statically compiled binaries for your application, and are about as minimal as you can realistically get. 

This example shows using a C compiler to create a static binary and running it on the static base image:

-- CODE language-bash -- Dockerfile # syntax=docker/dockerfile:1.4 FROM as build COPY < int main() { printf("Hello!"); } EOF RUN cc -static /hello.c -o /hello FROM COPY --from=build /hello /hello CMD ["/hello"]

You can find similar examples for Rust and Go.

A fairly common occurrence is for software to have further dependencies or for static compilation to not be (easily) possible. For that reason we have the glibc-dynamic Image which contains some common libraries, primarily glibc.

Using wolfi-base for flexibility and extensibility

But often this isn't enough – sometimes your application has more dependencies such as different libraries or runtimes. In some cases this will still be simple, for example, using a JDK image to build an application and a JRE image to provide the production runtime. In other cases it can be important to have more fine-grained control over the exact packages used to build and run an image, including controlling the versions. In these cases, the easiest solution is to reach for wolfi-base. Wolfi-base includes apk tooling and a shell so it's not as slimmed down as some of our other images, but this makes it easy to use in a traditional Dockerfile. For example, the golink project from Tailscale uses wolfi-base to install the required version of the Go compiler without worrying about being pushed to a new version when the Image updates:

-- CODE language-bash -- FROM --platform=$BUILDPLATFORM as build RUN apk update && apk add build-base git openssh go-1.20 WORKDIR /work COPY go.mod go.sum ./ RUN go mod download COPY . . ARG TARGETOS TARGETARCH TARGETVARIANT RUN \ if [ "${TARGETARCH}" = "arm" ] && [ -n "${TARGETVARIANT}" ]; then \ export GOARM="${TARGETVARIANT#v}"; \ fi; \ GOOS=${TARGETOS} GOARCH=${TARGETARCH} CGO_ENABLED=0 go build -v ./cmd/golink FROM ENV HOME /home/nonroot COPY --from=build /work/golink /golink ENTRYPOINT ["/golink"] CMD ["--sqlitedb", "/home/nonroot/golink.db", "--verbose"]

It's important to note that Wolfi is a rolling distribution. We don't do releases and we only publish a "latest" version. The few packages in wolfi-base are stable and highly unlikely to cause breakages, so it's safe to use "latest" in most use cases.

As this example uses a multi-stage build, our final image is still minimal. There is no shell or package manager present.

Matching build and runtime dependencies

Another common example is using Python. Python typically requires the build and runtime versions to match exactly. In the example below, we create a Python image where the builder and production Image are careful to use version 3.11:

-- CODE language-bash -- FROM as builder RUN apk add python3~3.11 py3-pip RUN adduser -D -u 65332 nonroot USER nonroot WORKDIR /app COPY requirements.txt . RUN pip install -r requirements.txt --user FROM RUN apk add python3~3.11 RUN adduser -D -u 65332 nonroot WORKDIR /app COPY --from=builder /home/nonroot/.local/lib/python3.11/site-packages /home/nonroot/.local/lib/python3.11/site-packages USER nonroot COPY . WORKDIR /app ENTRYPOINT [ "python", "/app/" ]

This Dockerfile takes advantage of the ~ syntax in apk to choose the version of Python.

Unfortunately, in this case the final Image isn't entirely minimal. It includes both a package manager and shell. If this is important to your use case, consider exploring a Chainguard Images subscription tier, which will give you access to tagged, minimal versions of the Python Chainguard Images.

Adding versioned tooling

Finally, another common use for wolfi-base is just to create an image with more tooling in it. For example, we can easily create an image with various network tooling for debugging and provide versions at different granularities if necessary:

-- CODE language-bash -- FROM RUN apk add wget \ curl~8.2.1 \ nmap~7 \ tcptraceroute \ tcpdump~4.99 \ netcat-openbsd

Building this, I get a 50 MB image which is pretty compact for all this tooling in a glibc-based image. If the package you need doesn't exist in Wolfi yet, you'll need to install or build it from upstream sources. To keep things clean and consistent, you can even turn this into an apk package using the melange tooling and contribute it back to Wolfi. Check out this article on Chainguard Academy to get started. 


This has been an in-depth tour of how to use Dockerfiles with Wolfi images. The key point to remember is that you can keep using your existing tooling with Wolfi images–you don't have to learn new tools and workflows.

Related articles

Ready to lock down your supply chain?

Talk to our customer obsessed, community-driven team.