performing-container-image-hardening skill (Anthropic-Cybersecurity-Skills)
- Install
- SKILL.md (verbatim)
- When to Use
- Prerequisites
- Workflow
- Step 1: Use Multi-Stage Builds to Minimize Image Size
- Step 2: Use Distroless Base Images
- Step 3: Remove Unnecessary Components
- Step 4: Configure Read-Only Filesystem
- Step 5: Pin Base Image by Digest
- Step 6: Validate Hardening with Automated Scanning
- Key Concepts
- Tools & Systems
- Common Scenarios
- Scenario: Reducing a 1.2GB Python Image to Under 150MB
- Output Format
- Other files in this skill
- assets/template.md (verbatim)
- Hardened Python Dockerfile
- Hardened Go Dockerfile (Distroless)
- Hadolint Configuration
- references/api-reference.md (verbatim)
- Libraries Used
- Installation
- Trivy Image Scanning
- Scan Image for Vulnerabilities
- Scan for Secrets in Image
- Scan for Misconfigurations
- Docker Image Inspection
- Inspect Image Metadata
- Check for Root User
- Hadolint Dockerfile Linting
- Hardening Checks
- Common Dockerfile Issues
- Output Format
- references/standards.md (verbatim)
- CIS Docker Benchmark v1.6.0 - Image Controls
- NIST SP 800-190 Application Container Security Guide
- Image Hardening (Section 4.1)
- OWASP Docker Security Cheat Sheet
- references/workflows.md (verbatim)
- Hardening Pipeline
- Base Image Selection Guide
- Dockerfile Hardening Checklist
What it does. 'Harden container images by minimizing attack surface, stripping unnecessary Part of mukul975/Anthropic-Cybersecurity-Skills (817 security skills) (mukul975/Anthropic-Cybersecurity-Skills).
| Upstream | mukul975/Anthropic-Cybersecurity-Skills |
| Skill file | skills/performing-container-image-hardening/SKILL.md |
| License | Apache-2.0 (skill folder LICENSE) |
| Author | mukul975 |
| Fetched | 2026-09-10 |
Install
npx skills add mukul975/Anthropic-Cybersecurity-Skills --skill performing-container-image-hardening, or copy the skill folder into~/.claude/skills/performing-container-image-hardening/.- Raw file:
curl -sL https://raw.githubusercontent.com/mukul975/Anthropic-Cybersecurity-Skills/HEAD/skills/performing-container-image-hardening/SKILL.md
SKILL.md (verbatim)
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 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"]
Step 2: Use Distroless Base Images
# 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"]
Step 3: Remove Unnecessary Components
# 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
Step 4: Configure Read-Only Filesystem
# 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
Step 5: Pin Base Image by Digest
# 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
Step 6: Validate Hardening with Automated Scanning
# 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
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: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%
Other files in this skill
- LICENSE
- assets/template.md
- references/api-reference.md
- references/standards.md
- references/workflows.md
- scripts/agent.py
- scripts/process.py
assets/template.md (verbatim)
Container Image Hardening Templates
Hardened Python Dockerfile
FROM python:3.12-slim-bookworm AS builder
WORKDIR /build
COPY requirements.txt .
RUN pip install --no-cache-dir --prefix=/install -r requirements.txt
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/* && \
groupadd -r appuser && useradd -r -g appuser -s /sbin/nologin appuser && \
find / -perm /6000 -type f -exec chmod a-s {} + 2>/dev/null || true
COPY --from=builder /install /usr/local
COPY --chown=appuser:appuser src/ /app/src/
USER appuser
WORKDIR /app
HEALTHCHECK --interval=30s --timeout=3s CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8080/health')" || exit 1
EXPOSE 8080
ENTRYPOINT ["python", "-m", "src.main"]
Hardened Go Dockerfile (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"]
Hadolint Configuration
# .hadolint.yaml
ignored:
- DL3008 # Pin versions in apt-get (use --no-install-recommends instead)
trustedRegistries:
- docker.io
- gcr.io
- ghcr.io
references/api-reference.md (verbatim)
API Reference: Container Image Hardening Audit
Libraries Used
| Library | Purpose |
|---|---|
subprocess |
Execute Trivy, Docker, and hadolint CLI commands |
json |
Parse vulnerability scan and inspection results |
re |
Analyze Dockerfile instructions |
pathlib |
Handle Dockerfile and image paths |
Installation
# Trivy vulnerability scanner
curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin
# Hadolint Dockerfile linter
wget -O /usr/local/bin/hadolint https://github.com/hadolint/hadolint/releases/latest/download/hadolint-Linux-x86_64
chmod +x /usr/local/bin/hadolint
# Docker CLI (already installed in most environments)
Trivy Image Scanning
Scan Image for Vulnerabilities
import subprocess
import json
def scan_image(image_name, severity="CRITICAL,HIGH"):
cmd = [
"trivy", "image",
"--format", "json",
"--severity", severity,
"--exit-code", "0",
image_name,
]
result = subprocess.run(cmd, capture_output=True, text=True, timeout=300)
return json.loads(result.stdout) if result.stdout else {}
Scan for Secrets in Image
def scan_secrets(image_name):
cmd = [
"trivy", "image",
"--format", "json",
"--scanners", "secret",
image_name,
]
result = subprocess.run(cmd, capture_output=True, text=True, timeout=300)
return json.loads(result.stdout) if result.stdout else {}
Scan for Misconfigurations
def scan_misconfig(image_name):
cmd = [
"trivy", "image",
"--format", "json",
"--scanners", "misconfig",
image_name,
]
result = subprocess.run(cmd, capture_output=True, text=True, timeout=300)
return json.loads(result.stdout) if result.stdout else {}
Docker Image Inspection
Inspect Image Metadata
def inspect_image(image_name):
cmd = ["docker", "inspect", image_name]
result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
data = json.loads(result.stdout)[0]
config = data.get("Config", {})
return {
"image": image_name,
"user": config.get("User", "root"),
"exposed_ports": list(config.get("ExposedPorts", {}).keys()),
"env_vars": config.get("Env", []),
"entrypoint": config.get("Entrypoint"),
"cmd": config.get("Cmd"),
"labels": config.get("Labels", {}),
"layers": len(data.get("RootFS", {}).get("Layers", [])),
"size_mb": round(data.get("Size", 0) / 1048576, 1),
}
Check for Root User
def check_non_root(image_name):
inspection = inspect_image(image_name)
user = inspection["user"]
return {
"image": image_name,
"runs_as_root": user in ("", "root", "0"),
"user": user or "root (default)",
"severity": "high" if user in ("", "root", "0") else "pass",
}
Hadolint Dockerfile Linting
def lint_dockerfile(dockerfile_path):
cmd = [
"hadolint",
"--format", "json",
str(dockerfile_path),
]
result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
findings = json.loads(result.stdout) if result.stdout else []
return [
{
"line": f["line"],
"code": f["code"],
"level": f["level"],
"message": f["message"],
}
for f in findings
]
Hardening Checks
Common Dockerfile Issues
def audit_dockerfile(dockerfile_path):
findings = []
with open(dockerfile_path) as f:
lines = f.readlines()
has_user = False
has_healthcheck = False
for i, line in enumerate(lines, 1):
stripped = line.strip()
if stripped.startswith("USER") and stripped.split()[-1] not in ("root", "0"):
has_user = True
if stripped.startswith("HEALTHCHECK"):
has_healthcheck = True
if stripped.startswith("FROM") and ":latest" in stripped:
findings.append({
"line": i, "severity": "medium",
"issue": "Using :latest tag — pin specific version",
})
if "ADD" in stripped and ("http://" in stripped or "https://" in stripped):
findings.append({
"line": i, "severity": "high",
"issue": "ADD with remote URL — use COPY + curl for verification",
})
if not has_user:
findings.append({"line": 0, "severity": "high", "issue": "No USER instruction — runs as root"})
if not has_healthcheck:
findings.append({"line": 0, "severity": "low", "issue": "No HEALTHCHECK instruction"})
return findings
Output Format
{
"image": "myapp:v1.2.3",
"vulnerabilities": {
"critical": 2,
"high": 8,
"medium": 15,
"low": 23
},
"runs_as_root": false,
"size_mb": 142.5,
"layers": 12,
"dockerfile_issues": 3,
"secrets_found": 0,
"findings": [
{
"type": "vulnerability",
"package": "openssl",
"installed": "3.0.2",
"fixed": "3.0.13",
"severity": "CRITICAL",
"cve": "CVE-2024-0727"
}
]
}
references/standards.md (verbatim)
Standards Reference: Container Image Hardening
CIS Docker Benchmark v1.6.0 - Image Controls
- 4.1: Create a user for the container
- 4.2: Use trusted base images
- 4.3: Do not install unnecessary packages
- 4.4: Scan and rebuild images for security patches
- 4.6: Add HEALTHCHECK instruction
- 4.7: Do not use update instructions alone
- 4.9: Use COPY instead of ADD
- 4.10: Do not store secrets in Dockerfiles
- 4.11: Install verified packages only
NIST SP 800-190 Application Container Security Guide
Image Hardening (Section 4.1)
- Use minimal base images to reduce attack surface
- Remove unnecessary tools, shells, and package managers
- Scan images for vulnerabilities before deployment
- Sign images for integrity verification
OWASP Docker Security Cheat Sheet
- Use multi-stage builds
- Run as non-root user
- Use read-only root filesystem
- Pin base images to digests
- Drop all Linux capabilities
- Enable seccomp profile
references/workflows.md (verbatim)
Workflow Reference: Container Image Hardening
Hardening Pipeline
Base Image Selection
│
▼
┌──────────────────┐
│ Multi-Stage Build │
│ (builder + prod) │
└──────┬───────────┘
│
▼
┌──────────────────┐
│ Remove Packages │
│ + Set User/Perms │
└──────┬───────────┘
│
▼
┌──────────────────┐
│ Trivy Scan │
│ + Hadolint │
└──────┬───────────┘
│
▼
┌──────────────────┐
│ CIS Benchmark │
│ Validation │
└──────────────────┘
Base Image Selection Guide
| Base Image | Size | Use Case | Packages |
|---|---|---|---|
| scratch | 0 MB | Static Go/Rust binaries | None |
| distroless/static | 2 MB | Static binaries + CA certs | ca-certificates |
| distroless/base | 20 MB | Dynamic binaries | glibc, libssl |
| alpine:3.19 | 7 MB | General minimal | musl, busybox |
| debian:bookworm-slim | 80 MB | Debian ecosystem | apt, glibc |
| ubuntu:24.04 | 78 MB | Ubuntu ecosystem | apt, glibc |
Dockerfile Hardening Checklist
- Multi-stage build separates build and runtime
- Minimal base image selected
- Non-root USER instruction present
- HEALTHCHECK instruction defined
- Base image pinned by digest
- COPY used instead of ADD
- No secrets in ENV or ARG
- Package cache cleaned (rm -rf /var/lib/apt/lists/*)
- Unnecessary packages removed
- setuid/setgid bits cleared
- Shell removed (if not needed)
Back to mukul975/Anthropic-Cybersecurity-Skills (817 security skills) or Agent skills.