Rajeshwari Vakharia

Rajeshwari Vakharia

Cloud Engineer • Systems, Go & AWS • Tech Educator
← Back to Articles

What is Docker Hardened Images and Why Should You Use It?

Originally published on DevOps.dev on Medium ↗.

Docker is by far the most widely adopted container runtime for running applications across cloud providers like AWS, GCP, and Azure. However, many production applications are deployed using default base images that contain hundreds of unneeded utilities, shell environments, and package managers.

This creates an unnecessarily wide attack surface. In this article, we explore what hardened Docker images are, why they are critical for enterprise security, and how to build them in practice.

The Problem with Default Images

When you use a generic base image like ubuntu:latest or golang:latest:

  • They ship with package managers (apt, apk), compilers, and debugging shells (/bin/sh, curl, wget) that an attacker can exploit if they achieve remote code execution.
  • By default, containers often run as root (UID 0). A container breakout vulnerability could compromise the host kernel.
  • Hundreds of dependencies mean daily CVE alerts in container registry vulnerability scanners.
Default Base (850 MB) ❌ Package Managers (apt, apk) ❌ Runs as root (UID 0) ❌ 40+ known CVE vulnerabilities
<!-- Hardened Image Box -->
<rect x="290" y="30" width="220" height="120" rx="8" fill="#1e293b" stroke="#34d399" stroke-width="2"/>
<text x="315" y="60" fill="#34d399" font-family="sans-serif" font-weight="bold" font-size="13">Hardened Image (15 MB)</text>
<text x="310" y="85" fill="#e2e8f0" font-family="sans-serif" font-size="11">✓ Scratch / Distroless runtime</text>
<text x="310" y="105" fill="#e2e8f0" font-family="sans-serif" font-size="11">✓ Dedicated non-root user</text>
<text x="310" y="125" fill="#e2e8f0" font-family="sans-serif" font-size="11">✓ 0 CVEs & read-only rootfs</text>
Figure 1: Comparison between default bloated images and a hardened container runtime.

4 Steps to Image Hardening

1. Multi-Stage Builds

Always separate the build environment from the runtime environment. Compilers, build caches, and test suites must never end up in production images.

2. Minimalist Distroless / Scratch Runtimes

Use Google’s gcr.io/distroless or scratch for compiled languages (Go, Rust). If using interpreted languages like Python or Node.js, use minimal Alpine or Chainguard images.

3. Enforce Non-Root Execution

Explicitly declare a dedicated non-root user using USER 10001. If the application does not need root privileges (and virtually no web service does), never let it execute as root.

4. Drop Unnecessary Linux Capabilities

Configure Docker or Kubernetes PodSecurityStandards to drop all default Linux capabilities (ALL) and only retain NET_BIND_SERVICE if binding to ports below 1024.

Practical Implementation: Hardened Dockerfile

Here is a production-ready example of a hardened, multi-stage Dockerfile:

# Stage 1: Build the application
FROM golang:1.24-alpine AS builder

WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download

COPY . .
# Strip debug symbols (-s -w) and disable CGO for pure static binary
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-w -s" -o server .

# Stage 2: Hardened Runtime
FROM gcr.io/distroless/static:nonroot

WORKDIR /
COPY --from=builder /app/server /server

# Explicitly use non-root user
USER 65532:65532

EXPOSE 8080
ENTRYPOINT ["/server"]

Conclusion

Hardening your Docker images is one of the highest-ROI security practices in modern DevOps. By reducing image size by up to 90% and running with non-root least privilege, you dramatically reduce attack surface while improving container boot times and deployment speed.