
Securing Docker images
Aikido Security's guide to hardening Docker images explains where container vulnerabilities come from (base OS layers like Debian/glibc that ship far more than an app needs), citing CVE-2023-4911 (Looney Tunables in glibc) and the xz-utils backdoor as cautionary examples for version-pinning decisions. It lays out hardening steps such as multi-stage builds, removing unused packages, non-root users, read-only filesystems, dropping Linux capabilities, pinning base images by digest, generating SBOMs, and signing/verifying images, then pitches Aikido Images, which backports CVE patches onto the same pinned base image version instead of forcing a migration to a new hardened distro.
What it is
A practitioner walkthrough (from Aikido) of Docker image hardening framed as ongoing vulnerability management, plus a specific critique of naive version strategies. Beyond the CVEs the summary lists, the page adds concrete decision logic about which version to pin and why stripping packages alone is insufficient.
The core distinction the summary omits
Removing unused packages only eliminates vulnerabilities in things you don't need. A vulnerable package your app actually depends on (e.g. glibc) can't be stripped and still requires a patch — and even a minimized image keeps accumulating new CVEs after build. So minimization and patching are two separate problems; doing the first does not solve the second.
Version strategy — both extremes fail
The page's actionable argument is that pinning-and-forgetting and always-upgrading are both wrong:
flowchart TD
A[Choose base version] --> B{Strategy}
B -->|Pin and forget| C[Node 20 EOL 2026-04-30:<br/>new CVEs never patched]
B -->|Always newest| D[xz-utils backdoor in<br/>5.6.0 / 5.6.1; 5.4 was safe]
B -->|Pin + record why + monitor| E[Know you run it and why;<br/>weigh exposure vs change cost]- Too old:
node:20reached end-of-life 2026-04-30; anything disclosed against that line since goes unpatched with no fix coming. - Too new: the xz-utils backdoor lived only in releases 5.6.0 and 5.6.1; teams on the older 5.4 line were never exposed. CISA's post-incident guidance was to downgrade, not upgrade.
- Alpine note: because Alpine uses musl rather than glibc, it was never exposed to Looney Tunables (CVE-2023-4911) at all — base-distro choice can eliminate a class of CVE outright.
What to do
- Start from a minimal/pre-hardened base — the highest-leverage choice.
- Use multi-stage builds so compiler, dev headers, and package manager stay in an early stage and never ship in the final image.
- Drop the shell (most apps exec their process directly) and runtime-unused utilities like
curl/wget. - If you pin, record why you chose that version and keep checking it against new disclosures, so exposure is a known decision rather than an accident.
Caveats
The closing pitch (Aikido delivering patched rebuilds of the same base version via PR) is vendor marketing; the hardening principles stand independently.
Organisations named here
- xz-utils
CVEs named here
- CVE-2023-4911
- CVE-2025-4373
Technologies named here
- Aikido Security
- glibc
- Node.js 24
- Aikido Images
- Node.js 20
- AutoFix
- Aikido Intel
- xz-utils 5.6.0 and 5.6.1
- xz-utils 5.4 line
- Aikido Libraries
- Aikido container image scanning
- debian:bookworm
- Alpine Linux
- SlimToolkit
- CAP_NET_RAW
- Docker
- Node.js
- Debian
- SBOM
Techniques named here
- Base image selection
- Multi-stage builds
- Removing unused packages
- Non-root container execution
- Read-only root filesystem
- Dropping Linux capabilities
- Secret handling via mounts
- Runtime secret injection
- Digest pinning
- SBOM generation
- Image signing and verification
- Immutable running containers
- Continuous rescanning
- Backporting fixes
- .dockerignore usage
Threat actors named here
- XZ Utils backdoor
Related in DevSecOps
JFrog: The Governance Gap Between Your Policy and Your Pipeline

Dev Machine Guard Now Supports Windows - StepSecurity

Socket: TanStack npm Packages Compromised
