Key Takeaways
- Never run as root inside containers
- Scan images for vulnerabilities in CI/CD
- Use minimal base images (distroless, alpine)
- Container escapes are real and dangerous
Contents
1. Container Security Fundamentals
Containers share the host kernel, making them less isolated than VMs. Security requires attention at every layer: image, runtime, orchestration, and infrastructure.
Container vs VM Isolation
- Containers: Shared kernel, namespace isolation, cgroups for resources
- VMs: Separate kernel, hypervisor isolation, hardware virtualization
2. Secure Image Building
# Dockerfile security best practices
FROM alpine:3.18 # Use minimal base image
# Create non-root user
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
# Copy only necessary files
COPY --chown=appuser:appgroup app /app
# Set non-root user
USER appuser
# Use specific versions, not 'latest'
# Don't store secrets in image
# Use multi-stage builds to minimize final image
# Multi-stage build example
FROM node:18 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM node:18-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
USER node
CMD ["node", "dist/server.js"]
3. Image Vulnerability Scanning
# Trivy - comprehensive scanner
trivy image myapp:latest
trivy image --severity HIGH,CRITICAL myapp:latest
# Grype
grype myapp:latest
# Snyk
snyk container test myapp:latest
# Docker Scout (built into Docker)
docker scout cves myapp:latest
# CI/CD integration
# Fail build if HIGH/CRITICAL vulns found
trivy image --exit-code 1 --severity HIGH,CRITICAL myapp:latest
4. Runtime Security
# Docker security options
docker run --read-only \
--security-opt=no-new-privileges \
--cap-drop=ALL \
--cap-add=NET_BIND_SERVICE \
--user 1000:1000 \
myapp:latest
# Seccomp profile (limit syscalls)
docker run --security-opt seccomp=/path/to/profile.json myapp
# AppArmor profile
docker run --security-opt apparmor=docker-default myapp
# Drop all capabilities, add only needed
# CAP_NET_BIND_SERVICE: bind to ports <1024
# CAP_CHOWN, CAP_SETUID: usually not needed
5. Container Escape Attacks
Container Escape Vectors
- Privileged containers: Full host access
- Docker socket mount: Create privileged containers
- Kernel exploits: Shared kernel vulnerabilities
- Sensitive host mounts: /etc, /root, /var/run/docker.sock
# DANGEROUS: Never do this in production
docker run --privileged -v /:/host myapp
# Attacker can: chroot /host && access everything
# Docker socket escape
docker run -v /var/run/docker.sock:/var/run/docker.sock myapp
# Inside container:
docker run -it --privileged --pid=host debian nsenter -t 1 -m -u -n -i sh
# Now running as root on host!
6. Kubernetes Security
# Pod Security Standards (restrictive)
apiVersion: v1
kind: Pod
metadata:
name: secure-pod
spec:
securityContext:
runAsNonRoot: true
runAsUser: 1000
fsGroup: 2000
seccompProfile:
type: RuntimeDefault
containers:
- name: app
image: myapp:latest
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop:
- ALL
# Network Policies (deny by default)
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: deny-all
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
7. Container Security Tools
- Trivy: Image and filesystem vulnerability scanning
- Falco: Runtime threat detection
- OPA/Gatekeeper: Policy enforcement
- Aqua/Prisma Cloud: Full container security platform
- kubesec: Kubernetes resource scanning
8. Hardening Checklist
Container Security Checklist
- ✅ Use minimal base images (distroless, alpine)
- ✅ Run as non-root user
- ✅ Scan images in CI/CD pipeline
- ✅ Drop all capabilities, add only needed
- ✅ Use read-only root filesystem
- ✅ Never run privileged containers
- ✅ Never mount Docker socket
- ✅ Sign and verify images
- ✅ Implement network policies
- ✅ Enable audit logging
FAQ
Are containers secure by default?
No. Default Docker configurations are optimized for convenience, not security. You must actively harden containers with non-root users, dropped capabilities, and proper image hygiene.