Agent Skills: Performing Container Image Hardening

>

UncategorizedID: plurigrid/asi/performing-container-image-hardening

Install this agent skill to your local

pnpm dlx add-skill https://github.com/plurigrid/asi/tree/HEAD/plugins/asi/skills/performing-container-image-hardening

Skill Files

Browse the full folder contents for performing-container-image-hardening.

Download Skill

Loading file tree…

plugins/asi/skills/performing-container-image-hardening/SKILL.md

Skill Metadata

Name
performing-container-image-hardening
Description
>

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:

  1. Switch to python:3.12-slim-bookworm as base (150MB) and install only required system libraries
  2. Use multi-stage build: compile C extensions in builder stage, copy wheels to production
  3. Pin numpy, pandas, and scipy to pre-built wheels to avoid build dependencies in production
  4. Remove pip, setuptools, and wheel from the final image
  5. Create non-root user and set filesystem permissions
  6. 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%