
Container Hardening
Practical container security techniques covering image hardening, runtime protection, resource limits, and defensive configurations for Docker and Kubernetes environments.
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 requiredRecommended minimal images:
gcr.io/distroless/*- Google's distroless images (no shell, package manager)alpine:*- ~5MB base with musl libcscratch- 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:latestUse 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: 1000Drop 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_SERVICEUse 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:latestNetwork 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: 8080Isolate Container Networks
# Create isolated network
docker network create --driver bridge --internal isolated-net
# Run container on isolated network
docker run --network isolated-net myapp:latestSecret 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 managementUse 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: trueMonitoring 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:latestRuntime 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: WARNINGKubernetes-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: restrictedService Account Hardening
apiVersion: v1
kind: ServiceAccount
metadata:
name: myapp
automountServiceAccountToken: false
---
apiVersion: v1
kind: Pod
spec:
serviceAccountName: myapp
automountServiceAccountToken: falseQuick 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: falseRelated Resources
- Docker Container Escape Techniques - Attack techniques these defenses prevent
- Kubernetes Security - Cloud-native security practices
References
MITRE ATT&CK Techniques (Defensive Context)
- T1610 - Deploy Container - Malicious container deployment
- T1611 - Escape to Host - Container escape techniques
- T1613 - Container and Resource Discovery - Container enumeration
- T1525 - Implant Internal Image - Supply chain attacks
Common Weakness Enumeration
- CWE-250 - Execution with Unnecessary Privileges - Running as root
- CWE-269 - Improper Privilege Management - Capability management
- CWE-522 - Insufficiently Protected Credentials - Secret management
Official Documentation
Last updated on
Containerization Fundamentals
Security-focused overview of Docker and Kubernetes architecture, covering isolation mechanisms, security boundaries, and common misconfigurations from an offensive security perspective.
Defense Evasion
Techniques for evading endpoint detection and response (EDR) systems, antivirus software, and security monitoring for red team operations and penetration testing.