Docker Security: Best Practices - Complete Production Guide
Docker container security is critical for protecting applications, data, and infrastructure. This comprehensive guide covers Docker security hardening, vulnerability management, runtime protection, and compliance best practices for production environments.
Table of Contents
- Introduction
- Security Principles
- Image Security
- Container Runtime Security
- Network Security
- Secrets Management
- Resource Limits
- Security Scanning
- Access Control
- Daemon Security
- Compliance and Auditing
- Production Checklist
- Conclusion
Introduction
Container security requires a multi-layered approach covering images, runtime configuration, networks, secrets, and the Docker daemon itself. Proper security practices prevent unauthorized access, data breaches, and resource abuse.
Security Attack Surface
- Images: Vulnerable dependencies and malware
- Runtime: Privilege escalation and container breakout
- Network: Unauthorized access and data interception
- Secrets: Exposed credentials and API keys
- Daemon: Docker socket exposure and misconfigurations
Security Principles
Least Privilege
Run containers with minimum necessary permissions:
# Don't run as root
FROM node:18-alpine
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
USER appuser
Defense in Depth
Multiple security layers:
- Secure base images
- Vulnerability scanning
- Runtime restrictions
- Network isolation
- Secrets encryption
Immutable Infrastructure
# Read-only root filesystem
docker run --read-only --tmpfs /tmp my-app
Image Security
Use Official Images
# Trusted sources only
FROM node:18-alpine # Official Node image
FROM nginx:alpine # Official Nginx image
# Avoid unknown sources
# FROM randomuser/suspicious-image # Don't use
Pin Specific Versions
# Bad - unpredictable
FROM node:latest
# Good - reproducible
FROM node:18.17.0-alpine3.18
# Include SHA256 digest
FROM node:18.17.0-alpine3.18@sha256:abc123...
Minimize Image Size
# Smaller images = smaller attack surface
FROM alpine:3.18
# Or distroless
FROM gcr.io/distroless/static-debian11
Multi-Stage Builds
# Build stage with tools
FROM golang:1.21 AS builder
WORKDIR /app
COPY . .
RUN go build -o app
# Minimal runtime
FROM gcr.io/distroless/static-debian11
COPY --from=builder /app/app /
ENTRYPOINT ["/app"]
Don't Include Secrets in Images
# Bad - secrets in image
ENV API_KEY=secret123
COPY .env /app/.env
# Good - inject at runtime
# docker run -e API_KEY=${API_KEY} my-app
Sign and Verify Images
# Enable Docker Content Trust
export DOCKER_CONTENT_TRUST=1
# Build signed image
docker build -t my-image:latest .
# Push signed image
docker push my-image:latest
Container Runtime Security
Run as Non-Root User
# Create and use non-root user
FROM alpine:3.18
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
USER appuser
WORKDIR /app
COPY --chown=appuser:appgroup . .
CMD ["./app"]
# Or specify at runtime
docker run --user 1000:1000 my-app
Read-Only Root Filesystem
# Prevent modifications to container filesystem
docker run -d \
--read-only \
--tmpfs /tmp \
--tmpfs /run \
nginx
# Docker Compose
services:
app:
image: my-app
read_only: true
tmpfs:
- /tmp
- /run
Drop Capabilities
# Drop all capabilities, add only what's needed
docker run -d \
--cap-drop ALL \
--cap-add NET_BIND_SERVICE \
nginx
Available capabilities:
NET_BIND_SERVICE: Bind to ports < 1024CHOWN: Change file ownershipDAC_OVERRIDE: Bypass file permission checksSETUID/SETGID: Change user/group ID
Disable New Privileges
# Prevent privilege escalation
docker run -d \
--security-opt=no-new-privileges:true \
my-app
Use Security Profiles
AppArmor
# Use AppArmor profile
docker run -d \
--security-opt apparmor=docker-default \
nginx
SELinux
# SELinux labels
docker run -d \
--security-opt label=level:s0:c100,c200 \
nginx
Seccomp
# Use seccomp profile
docker run -d \
--security-opt seccomp=/path/to/seccomp-profile.json \
my-app
Seccomp profile example:
{
"defaultAction": "SCMP_ACT_ERRNO",
"architectures": ["SCMP_ARCH_X86_64"],
"syscalls": [
{
"names": ["read", "write", "open", "close"],
"action": "SCMP_ACT_ALLOW"
}
]
}
Limit System Resources
# Prevent DoS attacks
docker run -d \
--memory="512m" \
--memory-swap="512m" \
--cpus="1.5" \
--pids-limit=100 \
my-app
Isolate Containers
# Use user namespaces
docker run -d --userns-remap=default my-app
Configure in /etc/docker/daemon.json:
{
"userns-remap": "default"
}
Network Security
Network Isolation
# Create isolated networks
docker network create --driver bridge frontend
docker network create --driver bridge backend
docker network create --driver bridge database
# Connect containers to appropriate networks
docker run -d --name web --network frontend nginx
docker run -d --name api --network backend my-api
docker run -d --name db --network database postgres
Disable Inter-Container Communication
# Create network with ICC disabled
docker network create \
--driver bridge \
--opt com.docker.network.bridge.enable_icc=false \
isolated-network
Encrypted Overlay Networks
# Create encrypted overlay network (Swarm)
docker network create \
--driver overlay \
--opt encrypted=true \
secure-overlay
Restrict Container Network Access
# No network access
docker run -d --network none my-batch-processor
# Use network policies in Kubernetes
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: deny-all
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
TLS for Communication
# Configure Docker daemon for TLS
dockerd \
--tlsverify \
--tlscacert=ca.pem \
--tlscert=server-cert.pem \
--tlskey=server-key.pem \
-H=0.0.0.0:2376
Secrets Management
Never Hardcode Secrets
# Bad
ENV DB_PASSWORD=mysecretpassword
# Good - pass at runtime
# docker run -e DB_PASSWORD=${DB_PASSWORD} my-app
Use Docker Secrets (Swarm)
# Create secret
echo "mypassword" | docker secret create db_password -
# Use in service
docker service create \
--name app \
--secret db_password \
my-app
Access in container:
# Secret available at /run/secrets/db_password
cat /run/secrets/db_password
Environment Variables
# Pass secrets via environment
docker run -d \
-e DB_PASSWORD=${DB_PASSWORD} \
-e API_KEY=${API_KEY} \
my-app
External Secrets Management
HashiCorp Vault
# Integrate with Vault
docker run -d \
-e VAULT_ADDR=https://vault.example.com \
-e VAULT_TOKEN=${VAULT_TOKEN} \
my-app
AWS Secrets Manager
# Use AWS secrets
docker run -d \
-e AWS_REGION=us-east-1 \
-e SECRET_ARN=arn:aws:secretsmanager:... \
my-app
Encrypt Environment Variables
# Use encrypted files
docker run -d \
--env-file encrypted.env \
my-app
Resource Limits
Memory Limits
# Set memory limit
docker run -d \
--memory="1g" \
--memory-reservation="512m" \
--memory-swap="2g" \
--oom-kill-disable=false \
my-app
CPU Limits
# Limit CPU usage
docker run -d \
--cpus="2.0" \
--cpu-shares=1024 \
--cpuset-cpus="0,1" \
my-app
Disk I/O Limits
# Limit disk operations
docker run -d \
--device-read-bps /dev/sda:1mb \
--device-write-bps /dev/sda:1mb \
my-app
Process Limits
# Limit number of processes
docker run -d \
--pids-limit=200 \
my-app
Security Scanning
Trivy Scanner
# Install Trivy
sudo apt-get install trivy
# Scan image
trivy image my-app:latest
# High and critical only
trivy image --severity HIGH,CRITICAL my-app:latest
# CI/CD integration
trivy image --exit-code 1 --severity CRITICAL my-app:latest
Clair Scanner
# Run Clair
docker run -d --name clair -p 6060:6060 quay.io/coreos/clair:latest
# Scan with clair-scanner
clair-scanner --ip $(hostname -I | awk '{print $1}') my-app:latest
Docker Scan
# Use Docker's built-in scanner
docker scan my-app:latest
# Scan and show base image recommendations
docker scan --file Dockerfile my-app:latest
Anchore
# Scan with Anchore
anchore-cli image add my-app:latest
anchore-cli image wait my-app:latest
anchore-cli image vuln my-app:latest all
Continuous Scanning
# GitHub Actions example
name: Security Scan
on: [push]
jobs:
scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Build image
run: docker build -t my-app .
- name: Run Trivy
uses: aquasecurity/trivy-action@master
with:
image-ref: 'my-app'
format: 'sarif'
output: 'trivy-results.sarif'
Access Control
Docker Socket Protection
# Never expose Docker socket
# Bad:
# docker run -v /var/run/docker.sock:/var/run/docker.sock ...
# Use Docker-in-Docker or alternative approaches
docker run --privileged docker:dind
User Namespaces
{
"userns-remap": "default"
}
# Restart Docker
sudo systemctl restart docker
Role-Based Access Control
# Use Docker Enterprise for RBAC
# Or implement custom authorization plugin
# Example: limit users to specific operations
{
"authorization-plugins": ["docker-authz-plugin"]
}
Audit Docker Commands
# Enable audit logging
sudo auditctl -w /usr/bin/docker -k docker
sudo auditctl -w /var/lib/docker -k docker
sudo auditctl -w /etc/docker -k docker
# View audit logs
sudo ausearch -k docker
Daemon Security
Secure Docker Daemon Configuration
{
"icc": false,
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3"
},
"live-restore": true,
"userland-proxy": false,
"no-new-privileges": true,
"seccomp-profile": "/etc/docker/seccomp-profile.json"
}
Enable TLS
{
"tls": true,
"tlsverify": true,
"tlscacert": "/etc/docker/ca.pem",
"tlscert": "/etc/docker/server-cert.pem",
"tlskey": "/etc/docker/server-key.pem"
}
Restrict Daemon Access
# Limit who can run Docker commands
sudo usermod -aG docker limited-user
# Better: use sudo for Docker commands
# Don't add users to docker group in production
Disable Legacy Features
{
"disable-legacy-registry": true
}
Compliance and Auditing
CIS Docker Benchmark
# Run Docker Bench Security
git clone https://github.com/docker/docker-bench-security.git
cd docker-bench-security
sudo sh docker-bench-security.sh
Docker Security Scanning
# Enable Docker Content Trust
export DOCKER_CONTENT_TRUST=1
# Verify image signatures
docker pull my-registry.com/image:tag
Log Everything
{
"log-driver": "syslog",
"log-opts": {
"syslog-address": "tcp://siem.example.com:514",
"tag": "docker/{{.Name}}"
}
}
Regular Audits
# Check running containers
docker ps --format "table {{.Names}}\t{{.Image}}\t{{.Ports}}"
# Review images
docker images
# Check volumes
docker volume ls
# Review networks
docker network ls
Production Checklist
Image Security
- Use official base images
- Pin specific versions with SHA256
- Scan for vulnerabilities
- No secrets in images
- Sign images with Docker Content Trust
- Minimal base images (Alpine/distroless)
- Multi-stage builds
- Regular image updates
Runtime Security
- Run as non-root user
- Read-only root filesystem
- Drop all capabilities, add only necessary ones
- Enable no-new-privileges
- Use security profiles (AppArmor/SELinux/Seccomp)
- Resource limits (CPU, memory, PIDs)
- Health checks configured
- Proper restart policies
Network Security
- Network isolation per application
- No unnecessary port exposure
- Encrypted overlay networks
- TLS for external communication
- Network policies implemented
Secrets Management
- No hardcoded secrets
- Use Docker secrets or external vault
- Encrypt secrets at rest and in transit
- Rotate secrets regularly
- Audit secret access
Access Control
- Docker socket never exposed
- User namespaces enabled
- RBAC implemented
- Audit logging enabled
- Principle of least privilege
Monitoring & Compliance
- Security scanning in CI/CD
- Runtime security monitoring
- Log aggregation configured
- CIS benchmark compliance
- Regular security audits
- Incident response plan
Conclusion
Docker security requires continuous attention across the entire container lifecycle. Implementing these best practices significantly reduces security risks in production environments.
Key Takeaways
- Defense in Depth: Multiple security layers
- Least Privilege: Minimum necessary permissions
- Immutability: Read-only root filesystems
- Scanning: Continuous vulnerability assessment
- Secrets: Never in images, always encrypted
- Monitoring: Audit and log everything
Security Hierarchy
- Secure Base Images: Start with trusted, minimal images
- Build Security: Scan and sign images
- Runtime Restrictions: Capabilities, read-only, non-root
- Network Isolation: Separate networks, encrypted communication
- Access Control: Protect Docker socket, user namespaces
- Monitoring: Continuous security scanning and auditing
Quick Security Commands
# Secure container run
docker run -d \
--read-only \
--tmpfs /tmp \
--cap-drop ALL \
--cap-add NET_BIND_SERVICE \
--security-opt=no-new-privileges:true \
--memory="512m" \
--cpus="1" \
--pids-limit=100 \
--user 1000:1000 \
my-app
# Scan image
trivy image --severity HIGH,CRITICAL my-app:latest
# Security audit
docker run -it --rm --pid host --userns host --cap-add audit_control \
-v /var/lib:/var/lib:ro \
-v /var/run/docker.sock:/var/run/docker.sock:ro \
-v /etc:/etc:ro \
--label docker_bench_security \
docker/docker-bench-security
Next Steps
- Audit: Run CIS Docker Benchmark
- Scan: Implement continuous security scanning
- Harden: Apply runtime security restrictions
- Monitor: Set up security monitoring and alerting
- Train: Educate team on security best practices
- Test: Perform penetration testing
- Update: Keep Docker and images updated
Security is an ongoing process. Regularly review and update your security practices to protect against evolving threats.


