When you pull an Alpine image, you are probably not thinking about what is actually running inside. Most teams see minimal and move on.
Here is the thing: Alpine Linux is built on BusyBox. One binary. Everything else links to it.
docker run --rm alpine:latest ls -la /bin/ | head -10
# lrwxrwxrwx 1 root root 12 /bin/ls -> /bin/busybox
# lrwxrwxrwx 1 root root 12 /bin/cat -> /bin/busybox
# lrwxrwxrwx 1 root root 12 /bin/cp -> /bin/busybox
Every utility you see is the same 1.3MB binary. That is your entire operating system.
The CVE Problem
When BusyBox has a vulnerability, your entire userspace has it. There is no compartmentalization.
Take CVE-2022-28391. It affected BusyBox is DHCP client. Any Alpine container acting as a DHCP client was vulnerable to remote code execution via a malicious DHCP response. This covered Kubernetes pods, CI runners, and network sidecars across the industry.
The fix most teams did: docker pull alpine:latest. But that does not guarantee you are on a patched version.
# Check BusyBox version
docker run --rm alpine:3.19 busybox | head -1
# Verify against security.alpinelinux.org
What You Can Do
1. Audit what you are actually running
Find Alpine images in your cluster and check for BusyBox presence.
2. Consider distroless for application containers
For Go or Rust services that compile statically, use distroless images. No shell, no package manager, no BusyBox. The image is slightly larger but your attack surface is smaller.
3. If you must use Alpine, pin and automate rebuilds
Use specific version tags like alpine:3.19 instead of :latest. Set up Renovate or Dependabot to watch Alpine release tags and rebuild when CVEs drop.
The Honest Tradeoff
Alpine is convenient. Distroless requires more build work. But if you are running internet-facing workloads or anything with elevated privileges, the convenience is not worth the invisible attack surface.
Go check your base images. See how many are Alpine. Then decide if you want to keep playing that game.
Top comments (0)