Security Best Practices
Container security is essential for production deployments. This lesson covers security practices for Docker images, containers, and the host environment.
Security Layers
┌─────────────────────────────────────────────────────────────────┐
│ Container Security Layers │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ Application Security (code, dependencies) │ │
│ └───────────────────────────────────────────────────────────┘ │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ Image Security (base image, packages, config) │ │
│ └───────────────────────────────────────────────────────────┘ │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ Container Runtime Security (privileges, capabilities) │ │
│ └───────────────────────────────────────────────────────────┘ │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ Host Security (Docker daemon, OS hardening) │ │
│ └───────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
Non-Root Containers
Never run containers as root in production:
FROM node:18-alpine
# Create non-root user
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
# Set ownership
WORKDIR /app
COPY --chown=appuser:appgroup . .
# Switch to non-root user
USER appuser
CMD ["node", "app.js"]
Verify User
# Check running user
docker exec container whoami
# Should not be "root"
# Run command as specific user
docker run -u 1000:1000 myapp
Read-Only Containers
Run containers with read-only filesystems:
# Read-only root filesystem
docker run --read-only myapp
# Allow specific writable directories
docker run --read-only \
--tmpfs /tmp \
--tmpfs /var/run \
-v logs:/app/logs \
myapp
# Docker Compose
services:
api:
image: myapi
read_only: true
tmpfs:
- /tmp
- /var/run
volumes:
- logs:/app/logs
Limit Capabilities
Drop unnecessary Linux capabilities:
# Drop all capabilities, add only what's needed
docker run --cap-drop ALL --cap-add NET_BIND_SERVICE myapp
# Common pattern
docker run \
--cap-drop ALL \
--cap-add CHOWN \
--cap-add SETGID \
--cap-add SETUID \
nginx
# Docker Compose
services:
api:
image: myapi
cap_drop:
- ALL
cap_add:
- NET_BIND_SERVICE
No New Privileges
Prevent privilege escalation:
docker run --security-opt no-new-privileges:true myapp
services:
api:
security_opt:
- no-new-privileges:true
Resource Limits
Prevent resource exhaustion attacks:
docker run \
--memory 512m \
--memory-swap 512m \
--cpus 0.5 \
--pids-limit 100 \
myapp
services:
api:
deploy:
resources:
limits:
cpus: '0.5'
memory: 512M
reservations:
cpus: '0.25'
memory: 256M
pids_limit: 100
Network Security
Disable Inter-Container Communication
# At daemon level
dockerd --icc=false
# Per network
docker network create --opt com.docker.network.bridge.enable_icc=false mynetwork
Use User-Defined Networks
services:
frontend:
networks:
- frontend
backend:
networks:
- frontend
- backend
database:
networks:
- backend # Not accessible from frontend
networks:
frontend:
backend:
internal: true # No external access
Limit Published Ports
services:
api:
ports:
# Only on localhost
- "127.0.0.1:3000:3000"
database:
# Don't publish database port
expose:
- "5432"
Image Security
Scan for Vulnerabilities
# Docker Scout
docker scout cves myimage:tag
docker scout recommendations myimage:tag
# Trivy
trivy image myimage:tag
# Snyk
snyk container test myimage:tag
Use Trusted Base Images
# Official images
FROM node:18-alpine
# Verified publisher images
FROM bitnami/postgresql:15
# Distroless (minimal)
FROM gcr.io/distroless/nodejs18-debian11
Sign Images
# Enable content trust
export DOCKER_CONTENT_TRUST=1
# Sign when pushing
docker push myregistry/myimage:tag
# Verify when pulling
docker pull myregistry/myimage:tag
Secrets Management
Never store secrets in images:
# BAD
ENV API_KEY=secret123
# BAD - even if deleted, it's in layer history
COPY secrets.txt /app/
RUN rm /app/secrets.txt
# GOOD - use BuildKit secrets
# syntax=docker/dockerfile:1.4
RUN --mount=type=secret,id=api_key \
API_KEY=$(cat /run/secrets/api_key) npm install
Logging and Auditing
Log Container Activity
services:
api:
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
labels: "service,environment"
Audit Docker Events
# Monitor Docker events
docker events
# Filter security-relevant events
docker events --filter 'event=start' --filter 'event=die'
Host Security
Protect Docker Socket
# Don't mount Docker socket in containers
# BAD
docker run -v /var/run/docker.sock:/var/run/docker.sock myapp
# If required, use read-only and limit access
docker run -v /var/run/docker.sock:/var/run/docker.sock:ro myapp
Use Rootless Docker
# Install rootless Docker
curl -fsSL https://get.docker.com/rootless | sh
# Run Docker without root privileges
export PATH=/home/$USER/bin:$PATH
export DOCKER_HOST=unix:///run/user/1000/docker.sock
Security Profiles
Seccomp
# Use default seccomp profile
docker run --security-opt seccomp=/path/to/profile.json myapp
# Check current profile
docker inspect --format='{{.HostConfig.SecurityOpt}}' container
AppArmor
# Use AppArmor profile
docker run --security-opt apparmor=docker-default myapp
Security Checklist
Image Security
- Using minimal base image
- No unnecessary packages
- Running as non-root user
- No secrets in image
- Image scanned for vulnerabilities
- Specific version tags (not :latest)
Runtime Security
- Read-only filesystem where possible
- Capabilities dropped
- no-new-privileges enabled
- Resource limits set
- Health checks defined
Network Security
- Only required ports published
- Published to localhost where possible
- Network isolation between tiers
- Internal networks for databases
Host Security
- Docker socket not mounted
- Docker daemon secured
- Audit logging enabled
- Regular security updates
Common Vulnerabilities
Prevent Container Escape
# Don't run privileged containers
docker run --privileged myapp # DANGEROUS
# Don't add sensitive capabilities
docker run --cap-add SYS_ADMIN myapp # DANGEROUS
# Use user namespaces
docker run --userns host myapp # Safer
Prevent Resource Abuse
services:
api:
deploy:
resources:
limits:
cpus: '1'
memory: 1G
ulimits:
nofile:
soft: 65536
hard: 65536
Key Takeaways
- Run containers as non-root users
- Use read-only filesystems when possible
- Drop all capabilities and add only what's needed
- Scan images regularly for vulnerabilities
- Implement network isolation between tiers
- Never store secrets in images
- Set resource limits to prevent DoS
- Use security profiles (seccomp, AppArmor)
- Keep Docker and base images updated
- Audit and log container activity

