Skill v1.0.1
currentAutomated scan100/100no file changes
name: performing-container-image-hardening description: 'Harden container images by minimizing attack surface, stripping unnecessary packages, implementing multi-stage builds, configuring non-root users, and applying CIS Docker Benchmark recommendations to produce secure, production-ready images. Use when building production container images, when compliance requires CIS Docker Benchmark adherence, or when shrinking image size to reduce vulnerability exposure from unused packages.
' domain: cybersecurity subdomain: devsecops tags:
- devsecops
- cicd
- container-hardening
- docker
- cis-benchmark
- secure-sdlc
version: 1.0.0 author: mahipal license: Apache-2.0 nist_csf:
- PR.PS-01
- GV.SC-07
- ID.IM-04
- PR.PS-04
mitre_attack:
- T1195
- T1554
- T1059.004
- T1610
- T1611
Performing Container Image Hardening
When to Use
- When building production container images that need minimal attack surface
- When compliance requires CIS Docker Benchmark adherence for container configurations
- When reducing image size to minimize vulnerability exposure from unused packages
- When implementing defense-in-depth for containerized workloads
- When migrating from fat base images to distroless or minimal images
Do not use for runtime container security monitoring (use Falco), for host-level Docker daemon hardening (use CIS Docker Benchmark host checks), or for container orchestration security (use Kubernetes security scanning).
Prerequisites
- Docker or BuildKit for multi-stage builds
- Base image options: distroless, Alpine, slim, or scratch
- Container scanning tool (Trivy) for validation
- CIS Docker Benchmark reference
Workflow
Step 1: Use Multi-Stage Builds to Minimize Image Size
# Build stage with all dependenciesFROM python:3.12-bookworm AS builderWORKDIR /buildCOPY requirements.txt .RUN pip install --no-cache-dir --prefix=/install -r requirements.txtCOPY src/ ./src/RUN python -m compileall src/# Production stage with minimal baseFROM python:3.12-slim-bookworm AS productionRUN apt-get update && \apt-get install -y --no-install-recommends libpq5 && \rm -rf /var/lib/apt/lists/* && \apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=falseCOPY --from=builder /install /usr/localCOPY --from=builder /build/src /app/srcRUN groupadd -r appuser && useradd -r -g appuser -d /app -s /sbin/nologin appuserRUN chown -R appuser:appuser /appUSER appuserWORKDIR /appHEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8080/health')" || exit 1EXPOSE 8080ENTRYPOINT ["python", "-m", "src.main"]
Step 2: Use Distroless Base Images
# Go application with distrolessFROM golang:1.22 AS builderWORKDIR /appCOPY go.* ./RUN go mod downloadCOPY . .RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-w -s" -o /server .FROM gcr.io/distroless/static-debian12:nonrootCOPY --from=builder /server /serverUSER nonroot:nonrootENTRYPOINT ["/server"]
Step 3: Remove Unnecessary Components
# Hardened image checklistFROM ubuntu:24.04 AS baseRUN apt-get update && \apt-get install -y --no-install-recommends \ca-certificates \libssl3 && \# Remove package manager to prevent runtime package installationapt-get purge -y --auto-remove apt dpkg && \rm -rf /var/lib/apt/lists/* \/var/cache/apt/* \/tmp/* \/var/tmp/* \/usr/share/doc/* \/usr/share/man/* \/usr/share/info/* \/root/.cache# Remove shells if not neededRUN rm -f /bin/sh /bin/bash /usr/bin/sh 2>/dev/null || true# Remove setuid/setgid binariesRUN find / -perm /6000 -type f -exec chmod a-s {} + 2>/dev/null || true
Step 4: Configure Read-Only Filesystem
# Kubernetes deployment with read-only root filesystemapiVersion: apps/v1kind: Deploymentmetadata:name: hardened-appspec:template:spec:securityContext:runAsNonRoot: truerunAsUser: 65534fsGroup: 65534seccompProfile:type: RuntimeDefaultcontainers:- name: appimage: app:hardenedsecurityContext:allowPrivilegeEscalation: falsereadOnlyRootFilesystem: truecapabilities:drop: ["ALL"]volumeMounts:- name: tmpmountPath: /tmp- name: cachemountPath: /app/cachevolumes:- name: tmpemptyDir:sizeLimit: 100Mi- name: cacheemptyDir:sizeLimit: 50Mi
Step 5: Pin Base Image by Digest
# Pin to exact image digest for reproducibilityFROM python:3.12-slim-bookworm@sha256:abcdef1234567890 AS production# This ensures the exact same base image is used every time
Step 6: Validate Hardening with Automated Scanning
# Scan hardened image with Trivytrivy image --severity HIGH,CRITICAL hardened-app:latest# Check CIS Docker Benchmark compliancedocker run --rm -v /var/run/docker.sock:/var/run/docker.sock \aquasec/docker-bench-security# Verify no root processesdocker run --rm hardened-app:latest whoami# Expected: appuser (NOT root)# Verify read-only filesystemdocker run --rm hardened-app:latest touch /test 2>&1# Expected: Read-only file system error
Key Concepts
| Term | Definition | |
|---|---|---|
| Multi-Stage Build | Docker build technique using multiple FROM stages to separate build and runtime, reducing final image size | |
| Distroless | Google-maintained minimal container images containing only the application and runtime dependencies | |
| Non-Root User | Running container processes as unprivileged user to limit impact of container escape exploits | |
| Read-Only Root | Mounting the container root filesystem as read-only to prevent runtime modification | |
| Image Digest | SHA256 hash uniquely identifying an exact image version, more precise than mutable tags | |
| Scratch Image | Empty Docker base image used for statically compiled binaries requiring no OS | |
| Security Context | Kubernetes pod/container-level security settings controlling privileges, filesystem, and capabilities |
Tools & Systems
- Docker BuildKit: Advanced Docker build engine supporting multi-stage builds and build secrets
- Distroless Images: Google's minimal container base images (static, base, java, python, nodejs)
- docker-bench-security: Script checking CIS Docker Benchmark compliance
- Trivy: Container image vulnerability and misconfiguration scanner
- Hadolint: Dockerfile linter enforcing best practices
Common Scenarios
Scenario: Reducing a 1.2GB Python Image to Under 150MB
Context: A data science team uses python:3.12 as base image (1.2GB) with scientific computing packages. The image has 200+ known CVEs from unnecessary system packages.
Approach:
- Switch to
python:3.12-slim-bookwormas base (150MB) and install only required system libraries - Use multi-stage build: compile C extensions in builder stage, copy wheels to production
- Pin numpy, pandas, and scipy to pre-built wheels to avoid build dependencies in production
- Remove pip, setuptools, and wheel from the final image
- Create non-root user and set filesystem permissions
- Validate with Trivy: expect CVE count to drop from 200+ to under 20
Pitfalls: Some Python packages require shared libraries at runtime (libgomp, libstdc++). Test the application thoroughly after removing system packages. Alpine-based images use musl libc which can cause compatibility issues with numpy and pandas.
Output Format
Container Image Hardening Report==================================Image: app:hardenedBase: python:3.12-slim-bookwormDate: 2026-02-23SIZE COMPARISON:Before hardening: 1,247 MB (python:3.12)After hardening: 143 MB (python:3.12-slim + multi-stage)Reduction: 88.5%SECURITY CHECKS:[PASS] Non-root user configured (appuser:1000)[PASS] HEALTHCHECK instruction present[PASS] No setuid/setgid binaries found[PASS] Package manager removed[PASS] Base image pinned by digest[PASS] No shell access (/bin/sh removed)[WARN] /tmp writable (emptyDir mounted)VULNERABILITY COMPARISON:Before: 234 CVEs (12 Critical, 45 High)After: 18 CVEs (0 Critical, 3 High)Reduction: 92.3%