Install any skill in seconds. Free to start, no credit card required.
Get Started Free →This skill covers hardening container images by minimizing attack surface, removing unnecessary packages, implementing multi-stage builds, configuring non-root users, and applying CIS Docker Benchmark recommendations to produce secure production-ready images.
.claude/skills/performing-container-image-hardening/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-16 | ✗→✓ | ▲ Improved | — | — |
| case-19 | ✗→✓ | ▲ Improved | — | — |
| case-09 | ✗→✓ | ▲ Improved | — | — |
| case-21 | ✗→✓ | ▲ Improved | — | — |
| case-18 | ✗→✓ | ▲ Improved | — | — |
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).
dockerfile# Build stage with all dependencies FROM python:3.12-bookworm AS builder WORKDIR /build COPY requirements.txt . RUN pip install --no-cache-dir --prefix=/install -r requirements.txt COPY src/ ./src/ RUN python -m compileall src/ # Production stage with minimal base FROM python:3.12-slim-bookworm AS production RUN 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=false COPY --from=builder /install /usr/local COPY --from=builder /build/src /app/src RUN groupadd -r appuser && useradd -r -g appuser -d /app -s /sbin/nologin appuser RUN chown -R appuser:appuser /app USER appuser WORKDIR /app HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8080/health')" || exit 1 EXPOSE 8080 ENTRYPOINT ["python", "-m", "src.main"]
dockerfile# Go application with distroless FROM golang:1.22 AS builder WORKDIR /app COPY go.* ./ RUN go mod download COPY . . RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-w -s" -o /server . FROM gcr.io/distroless/static-debian12:nonroot COPY --from=builder /server /server USER nonroot:nonroot ENTRYPOINT ["/server"]
dockerfile# Hardened image checklist FROM ubuntu:24.04 AS base RUN apt-get update && \ apt-get install -y --no-install-recommends \ ca-certificates \ libssl3 && \ # Remove package manager to prevent runtime package installation apt-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 needed RUN rm -f /bin/sh /bin/bash /usr/bin/sh 2>/dev/null || true # Remove setuid/setgid binaries RUN find / -perm /6000 -type f -exec chmod a-s {} + 2>/dev/null || true
yaml# Kubernetes deployment with read-only root filesystem apiVersion: apps/v1 kind: Deployment metadata: name: hardened-app spec: template: spec: securityContext: runAsNonRoot: true runAsUser: 65534 fsGroup: 65534 seccompProfile: type: RuntimeDefault containers: - name: app image: app:hardened securityContext: allowPrivilegeEscalation: false readOnlyRootFilesystem: true capabilities: drop: ["ALL"] volumeMounts: - name: tmp mountPath: /tmp - name: cache mountPath: /app/cache volumes: - name: tmp emptyDir: sizeLimit: 100Mi - name: cache emptyDir: sizeLimit: 50Mi
dockerfile# Pin to exact image digest for reproducibility FROM python:3.12-slim-bookworm@sha256:abcdef1234567890 AS production # This ensures the exact same base image is used every time
bash# Scan hardened image with Trivy trivy image --severity HIGH,CRITICAL hardened-app:latest # Check CIS Docker Benchmark compliance docker run --rm -v /var/run/docker.sock:/var/run/docker.sock \ aquasec/docker-bench-security # Verify no root processes docker run --rm hardened-app:latest whoami # Expected: appuser (NOT root) # Verify read-only filesystem docker run --rm hardened-app:latest touch /test 2>&1 # Expected: Read-only file system error
| 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 |
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:
python:3.12-slim-bookworm as base (150MB) and install only required system librariesPitfalls: 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.
Container Image Hardening Report
==================================
Image: app:hardened
Base: python:3.12-slim-bookworm
Date: 2026-02-23
SIZE 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%| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-14 | pass→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-11 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-20 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-13 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-17 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-16 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-19 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-12 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-09 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-21 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-18 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-07 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-01 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-23 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-05 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-02 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-15 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-10 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-08 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-03 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-04 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-06 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-22 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 23 cases were attempted. The headline lift of +35 percentage points is the difference between those two pass rates over the 23 comparable cases.
The per-case answers from this run were removed by the retention sweep, so the case table below shows the verdicts without the text either arm produced. The counts above were recorded at the time and are unaffected. Answers are now kept for 180 days.
Other measured skills in the registry, with their headline benchmark lift.