Container security hardening techniques

Container Hardening

Practical container security techniques covering image hardening, runtime protection, resource limits, and defensive configurations for Docker and Kubernetes environments.

Mar 20, 2026
Updated Dec 11, 2025
2 min read

Introduction

Containers share the host OS kernel, which creates inherent security trade-offs. A compromised container with insufficient isolation can enable lateral movement, data exfiltration, or full host compromise. Container hardening reduces these risks by minimizing the attack surface and enforcing strict isolation boundaries.

This guide covers defensive techniques for securing containerized environments, from image creation through runtime configuration.

Defense in Depth

Container hardening is one layer of defense. Combine these techniques with network segmentation, runtime monitoring, and vulnerability scanning for effective security.

Image Hardening

Use Minimal Base Images

Start with lean base images to reduce the attack surface. Smaller images contain fewer packages, libraries, and potential vulnerabilities.

# Prefer distroless or Alpine images
FROM gcr.io/distroless/static-debian12
# Or
FROM alpine:3.19

# Avoid full OS images like ubuntu:latest or debian:latest
# unless specific packages are required

Recommended minimal images:

  • gcr.io/distroless/* - Google's distroless images (no shell, package manager)
  • alpine:* - ~5MB base with musl libc
  • scratch - Empty image for statically compiled binaries

Scan Images for Vulnerabilities

Integrate vulnerability scanning into CI/CD pipelines to catch issues before deployment.

# Trivy - Fast vulnerability scanner
trivy image myapp:latest

# Grype - Anchore's scanner
grype myapp:latest

# Docker Scout (built into Docker)
docker scout cve myapp:latest

Use Multi-Stage Builds

Separate build dependencies from runtime to minimize the final image:

# Build stage
FROM golang:1.22 AS builder
WORKDIR /app
COPY . .
RUN CGO_ENABLED=0 go build -o myapp

# Runtime stage - minimal image
FROM gcr.io/distroless/static-debian12
COPY --from=builder /app/myapp /
USER nonroot:nonroot
ENTRYPOINT ["/myapp"]

Pin Image Versions

Avoid latest tags in production. Pin specific versions or SHA digests for reproducibility:

# Pin version
FROM nginx:1.25.4-alpine

# Or pin SHA for immutability
FROM nginx@sha256:abc123...

Runtime Hardening

Run as Non-Root User

Never run containers as root. Create and use dedicated application users:

# In Dockerfile
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
USER appuser
# At runtime
docker run --user 1000:1000 myapp:latest

# Kubernetes
securityContext:
  runAsNonRoot: true
  runAsUser: 1000
  runAsGroup: 1000

Drop Capabilities

Containers inherit capabilities by default. Drop all and add only what's required:

# Docker - drop all, add specific
docker run --cap-drop=ALL --cap-add=NET_BIND_SERVICE myapp

# Kubernetes Pod Security Context
securityContext:
  capabilities:
    drop:
      - ALL
    add:
      - NET_BIND_SERVICE

Use Read-Only Root Filesystem

Prevent runtime modifications by mounting the filesystem as read-only:

# Docker
docker run --read-only myapp:latest

# With temp directory for application needs
docker run --read-only --tmpfs /tmp:rw,noexec,nosuid myapp:latest
# Kubernetes
securityContext:
  readOnlyRootFilesystem: true
volumeMounts:
  - name: tmp
    mountPath: /tmp
volumes:
  - name: tmp
    emptyDir: {}

Limit Resources

Prevent resource exhaustion attacks with CPU and memory limits:

# Docker
docker run --memory=512m --cpus=0.5 myapp:latest
# Kubernetes
resources:
  limits:
    memory: "512Mi"
    cpu: "500m"
  requests:
    memory: "256Mi"
    cpu: "250m"

Disable Privilege Escalation

Prevent processes from gaining additional privileges:

# Kubernetes
securityContext:
  allowPrivilegeEscalation: false
# Docker
docker run --security-opt=no-new-privileges myapp:latest

Network Security

Use Network Policies

Restrict container-to-container communication in Kubernetes:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: deny-all-ingress
spec:
  podSelector: {}
  policyTypes:
    - Ingress
  ingress: []
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-specific
spec:
  podSelector:
    matchLabels:
      app: backend
  ingress:
    - from:
        - podSelector:
            matchLabels:
              app: frontend
      ports:
        - port: 8080

Isolate Container Networks

# Create isolated network
docker network create --driver bridge --internal isolated-net

# Run container on isolated network
docker run --network isolated-net myapp:latest

Secret Management

Never Embed Secrets in Images

Secrets in images persist in layer history and can be extracted:

# BAD - Secret in image
ENV API_KEY=secret123

# GOOD - Use runtime secrets
# Pass at runtime via environment or secret management

Use Secret Management Solutions

# Docker Secrets (Swarm)
echo "mysecret" | docker secret create db_password -
docker service create --secret db_password myapp

# Kubernetes Secrets
kubectl create secret generic db-creds \
  --from-literal=password=mysecret
# Mount as volume (preferred over env vars)
volumes:
  - name: secret-volume
    secret:
      secretName: db-creds
volumeMounts:
  - name: secret-volume
    mountPath: /secrets
    readOnly: true

Monitoring and Detection

Enable Container Logging

Configure centralized logging for security visibility:

# Docker logging drivers
docker run --log-driver=json-file \
  --log-opt max-size=10m \
  --log-opt max-file=3 \
  myapp:latest

Runtime Security Monitoring

Deploy runtime security tools to detect anomalous behavior:

  • Falco - Kernel-level syscall monitoring
  • Sysdig - Container visibility and forensics
  • Aqua Security - Runtime protection
  • Tetragon - eBPF-based security observability
# Example Falco rule for detecting shell spawns
- rule: Shell Spawned in Container
  desc: Detect shell execution in container
  condition: >
    spawned_process and container and
    proc.name in (bash, sh, zsh, ksh)
  output: >
    Shell spawned in container
    (user=%user.name container=%container.name shell=%proc.name)
  priority: WARNING

Kubernetes-Specific Hardening

Pod Security Standards

Enforce security baselines with Pod Security Admission:

apiVersion: v1
kind: Namespace
metadata:
  name: production
  labels:
    pod-security.kubernetes.io/enforce: restricted
    pod-security.kubernetes.io/audit: restricted
    pod-security.kubernetes.io/warn: restricted

Service Account Hardening

apiVersion: v1
kind: ServiceAccount
metadata:
  name: myapp
automountServiceAccountToken: false
---
apiVersion: v1
kind: Pod
spec:
  serviceAccountName: myapp
  automountServiceAccountToken: false

Quick Reference: Hardened Container

# Kubernetes Pod with hardening best practices
apiVersion: v1
kind: Pod
metadata:
  name: hardened-app
spec:
  securityContext:
    runAsNonRoot: true
    runAsUser: 1000
    runAsGroup: 1000
    fsGroup: 1000
    seccompProfile:
      type: RuntimeDefault
  containers:
    - name: app
      image: myapp:1.0.0@sha256:abc123
      securityContext:
        allowPrivilegeEscalation: false
        readOnlyRootFilesystem: true
        capabilities:
          drop:
            - ALL
      resources:
        limits:
          memory: "512Mi"
          cpu: "500m"
        requests:
          memory: "256Mi"
          cpu: "250m"
      volumeMounts:
        - name: tmp
          mountPath: /tmp
  volumes:
    - name: tmp
      emptyDir: {}
  automountServiceAccountToken: false

References

MITRE ATT&CK Techniques (Defensive Context)

Common Weakness Enumeration

Official Documentation

Last updated on