Container Security: Complete Enterprise Hardening Guide

Containers introduced new attack surfaces: base image vulnerabilities, runtime exploits, secrets in environment variables, and privilege escalation. This guide provides a comprehensive security framework covering the entire container lifecycle—from build to runtime—based on NIST, CIS Benchmarks, and production incident learnings.

Container Security Layers

flowchart TB
    subgraph Build ["Build Time"]
        BaseImage["Base Image Scanning"]
        SBOM["SBOM Generation"]
        Signing["Image Signing"]
    end
    
    subgraph Registry ["Registry"]
        Scan["Vulnerability Scanning"]
        Policy["Admission Policies"]
    end
    
    subgraph Runtime ["Runtime"]
        Network["Network Policies"]
        Seccomp["Seccomp/AppArmor"]
        Readonly["Read-Only FS"]
    end
    
    Build --> Registry --> Runtime
    
    style BaseImage fill:#FFCDD2,stroke:#C62828
    style Scan fill:#FFF3E0,stroke:#E65100
    style Network fill:#C8E6C9,stroke:#2E7D32

Build-Time Security

Minimal Base Images

# ❌ Bad: Full OS with package manager
FROM ubuntu:22.04

# ✅ Good: Minimal distroless image
FROM gcr.io/distroless/dotnet:6.0

# ✅ Better: Scratch for statically compiled
FROM scratch
COPY --from=build /app/binary /binary
ENTRYPOINT ["/binary"]

Distroless images have 80% fewer CVEs than traditional base images because they exclude shells, package managers, and utilities.

Multi-Stage Builds

# Build stage: Full SDK with tools
FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
WORKDIR /src
COPY . .
RUN dotnet publish -c Release -o /app

# Runtime stage: Minimal runtime only
FROM mcr.microsoft.com/dotnet/aspnet:6.0
WORKDIR /app
COPY --from=build /app .
# Run as non-root user
USER 1000:1000
ENTRYPOINT ["dotnet", "MyApp.dll"]

SBOM Generation

# Generate SBOM with Syft
syft packages myimage:latest -o spdx-json > sbom.json

# Include in image labels
docker build --label "sbom=$(cat sbom.json | base64)" .

Registry Security

Vulnerability Scanning

# GitHub Actions: Scan on push
- name: Scan image
  uses: aquasecurity/trivy-action@master
  with:
    image-ref: 'myregistry/myimage:${{ github.sha }}'
    format: 'sarif'
    output: 'trivy-results.sarif'
    severity: 'CRITICAL,HIGH'
    exit-code: '1'  # Fail build on vulnerabilities

Image Signing (Sigstore/Cosign)

# Sign image
cosign sign --key cosign.key myregistry/myimage:v1.0.0

# Verify in admission controller
cosign verify --key cosign.pub myregistry/myimage:v1.0.0

Runtime Security

Pod Security Standards

apiVersion: v1
kind: Pod
metadata:
  name: secure-pod
spec:
  securityContext:
    runAsNonRoot: true
    runAsUser: 1000
    fsGroup: 2000
    seccompProfile:
      type: RuntimeDefault
  containers:
  - name: app
    image: myapp:v1
    securityContext:
      allowPrivilegeEscalation: false
      readOnlyRootFilesystem: true
      capabilities:
        drop: ["ALL"]
    resources:
      limits:
        memory: "256Mi"
        cpu: "500m"

Network Policies

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: api-isolation
spec:
  podSelector:
    matchLabels:
      app: api
  policyTypes:
  - Ingress
  - Egress
  ingress:
  - from:
    - podSelector:
        matchLabels:
          app: frontend
    ports:
    - port: 8080
  egress:
  - to:
    - podSelector:
        matchLabels:
          app: database
    ports:
    - port: 5432

Secrets Management

# ❌ Never: Environment variables
env:
  - name: DB_PASSWORD
    value: "mysecret"  # Visible in pod spec!

# ✅ Better: External secrets (AWS/Azure)
containers:
- name: app
  volumeMounts:
  - name: secrets
    mountPath: "/secrets"
    readOnly: true
volumes:
- name: secrets
  csi:
    driver: secrets-store.csi.k8s.io
    volumeAttributes:
      secretProviderClass: "azure-keyvault"

Key Takeaways

  • Use minimal/distroless base images
  • Multi-stage builds separate build tools from runtime
  • Scan images in CI/CD before pushing
  • Sign images with Sigstore/Cosign
  • Enable Pod Security Standards (Restricted)
  • Network Policies implement microsegmentation
  • Never store secrets in environment variables

Discover more from C4: Container, Code, Cloud & Context

Subscribe to get the latest posts sent to your email.

Leave a comment

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.