Wolfi's split packages and the mistakes they cause, the secure way
You typed apk add gnupg, the install said OK, and gpg is not there. You're not crazy, you heard right. On Wolfi, the GnuPG meta package installs an SBOM file and nothing else.
The short answer
Wolfi splits each upstream project into small packages: gpg, gpgv and gpg-agent are separate, and the gnupg meta package pulls none of them. Install by command with apk add cmd:gpgv, list exactly the packages an image needs, and test every command the image must run in CI.
On this page
What goes wrong
Wolfi, the distribution under Chainguard images, builds one upstream project into many small packages. That is good for security: an image gets only the files it uses, and a scanner reports only on what is there.
It also breaks habits from Debian and Alpine. Three mistakes come up again and again:
- Installing the meta package and assuming it pulls in the whole suite.
gnupgon Wolfi installs no binaries. - Installing the obvious package and missing a helper.
gpgalone cannot create a key, becausegpg-agentis a separate package. - "Fixing" a missing command by moving to a
-devimage or adding a whole toolchain, which brings back the shell, the package manager and the vulnerabilities you left Debian to avoid.
The security cost is quiet. A signature check in a Dockerfile that calls
gpgv, wrapped in || true or a loose if, passes when gpgv is not
installed. The image builds, and nothing was verified.
What the docs say
Packages are designed to be granular and independent, to support minimal images
Source: Chainguard Academy, Wolfi overview
Although compiling said extensions as built-in packages makes for a simpler build, it also increases the size of the original package and creates a wider surface for possible vulnerabilities.
Source: Chainguard Academy, Building a Wolfi package
GNU Privacy Guard 2 - meta package for full GnuPG suite
Source: wolfi-dev/os, gnupg.yaml
The recipe calls gnupg a meta package for the full suite, but its runtime
dependencies are only merged-lib, merged-usrsbin and wolfi-baselayout.
The binaries live in subpackages (gpg, gpgv, gpg-agent, gpgsm,
gnupg-dirmngr and others) that nothing pulls in for you. Neither the Wolfi
overview nor the recipe warns about this.
The secure configuration
Install by command, not by guess, and pin the result.
# Build stage: wolfi-base has apk and a shell.
FROM cgr.dev/chainguard/wolfi-base@sha256:<digest> AS verify
# Ask apk for the package that provides each command you will call.
# cmd:<name> resolves to the package that ships /usr/bin/<name>.
RUN apk add --no-cache cmd:gpgv cmd:curl
# Fail loudly if a command is missing, before it is used.
RUN for c in gpgv curl; do command -v "$c" >/dev/null || { echo "missing: $c"; exit 1; }; done
# Verify with gpgv against a keyring you ship, never "|| true".
COPY release-signing-key.gpg /keys/release.gpg
RUN curl -fsSLO https://downloads.example.com/tool-1.2.3.tar.gz \
&& curl -fsSLO https://downloads.example.com/tool-1.2.3.tar.gz.sig \
&& gpgv --keyring /keys/release.gpg tool-1.2.3.tar.gz.sig tool-1.2.3.tar.gz \
&& tar -xzf tool-1.2.3.tar.gz -C /opt
# Runtime stage: no apk, no shell, no gpg.
FROM cgr.dev/chainguard/static@sha256:<digest>
COPY --from=verify /opt/tool /opt/tool
USER 65532:65532
ENTRYPOINT ["/opt/tool/bin/tool"]Once the build works, replace each cmd: entry with the package name that
apk search -q cmd:<name> printed, so the image says exactly what it
contains. Common surprises:
| You want | Wolfi package |
|---|---|
gpg (sign, encrypt) | gpg plus gpg-agent for any key operation |
gpgv (verify only) | gpgv |
ssh-keygen | openssh-keygen (not in openssh-client) |
dig, nslookup | bind-tools |
ps | procps |
useradd | shadow (busybox adduser is already in wolfi-base) |
envsubst | gettext-envsubst (smaller than all of gettext) |
pip | py3.14-pip (one package per Python version) |
For signature checks, prefer gpgv over gpg: it only verifies, needs no
agent, and is the smaller package.
Prove it
In a disposable wolfi-base container, install the meta package first:
apk add gnupg
apk info -R gnupg ; apk info -L gnupg
command -v gpg gpgv gpg-agentgnupg-2.4.9-r11 depends on:
merged-lib
merged-usrsbin
wolfi-baselayout
gnupg-2.4.9-r11 contains:
var/lib/db/sbom/gnupg-2.4.9-r11.spdx.json
gpg not found
gpgv not found
gpg-agent not foundThe install succeeds and ships one file, its own SBOM. Now the obvious package:
apk add gpg
gpg --batch --passphrase "" --quick-gen-key [email protected]gpg /usr/bin/gpg
gpgv not found
gpg-agent not found
gpg: can't connect to the gpg-agent: Configuration error
gpg: agent_genkey failed: No agent running
gpg: key generation failed: No agent runningAsk apk which packages ship the missing commands, and install by command:
apk search -q cmd:gpg-agent cmd:gpgv
apk add cmd:gpg-agent cmd:gpgvgpg-agent
gpgv
gpg /usr/bin/gpg
gpgv /usr/bin/gpgv
gpg-agent /usr/bin/gpg-agent
key generatedThe same lookup for other commands that trip people up:
ssh-keygen -> openssh-keygen
dig -> bind-tools
ps -> procps
useradd -> shadow
envsubst -> gettext gettext-envsubstMistakes people make
Trusting a meta package
On Debian, gnupg pulls in the suite. On Wolfi, it pulls in nothing you can
run. Check with apk info -R <package> before you rely on a meta package.
Signature checks that pass when the tool is missing
gpgv ... || true, or a script that checks the exit code of the wrong
command, turns "gpgv: not found" into "verified". Check that each command
exists before the step that uses it, and let the verify step fail the build.
Jumping to the -dev image in production
The -dev variants add a shell and apk so you can build. Use them in a build
stage. Copy the result into a runtime image without a shell or package
manager.
Installing the whole toolchain for one command
apk add gettext to get envsubst, or openssh to get ssh-keygen, adds
packages that every scan will report on. Install the subpackage.
Floating package versions in a pinned image
A pinned base digest with apk add curl still installs whatever version is
current on build day. Pin versions where builds must be repeatable
(apk add curl=<version>), or build the image with apko from a locked
package list.
Checklist
- Every
apk addnames the package that ships the command, found withapk search -q cmd:<name>. - No image relies on the
gnupgmeta package for binaries. - Signature checks use
gpgvwith a shipped keyring and fail the build on error. - A build step checks with
command -vthat each needed command exists. -devimages are used only in build stages.- The runtime stage has no apk and no shell.
- Package versions are pinned where builds must be repeatable.
- The base image is pinned by digest.
Wolfi gives you exactly the packages you ask for, not the ones you meant. Ask by command and you get both.
H2-CSDE
Learn it on a live range
Dependencies and vendoring, in DevSecOps and Supply Chain: a real host in your browser, and every objective checked on the machine.
Start freeH2 Scanner
Want this caught before it merges?
The H2 Scanner runs in your CI and flags the weaknesses pages like this one warn about, on every pull request.
Talk to us