Chapter 8 Docker Security and Best Practices
Chapter 8: Docker Security and Best Practices
Learning Objectives
- Understand Docker security threat models and attack vectors
- Learn to configure a secure container runtime environment
- Implement image security scanning and vulnerability detection
- Master access control and permission management
- Learn Docker security best practices
Detailed Knowledge Points
8.1 Docker Security Threat Model
8.1.1 Container Security Threats
Main Threat Types:
- Container escape
- Privilege escalation
- Denial of service attacks
- Malicious images
- Data leakage
- Network attacks
Threat Examples:
# Dangerous privileged container execution
docker run --privileged -it ubuntu bash
# Insecure host mount
docker run -v /:/host ubuntu bash
# Using an untrusted image
docker run untrusted/malicious-image
8.1.2 Attack Vector Analysis
Container Escape Scenarios:
# 1. Kernel vulnerability exploitation
# Check kernel version
uname -r
# 2. Docker daemon attack
# Check Docker socket permissions
ls -la /var/run/docker.sock
# 3. Container configuration errors
docker inspect container_name | grep -i privileged
8.2 Container Security Configuration
8.2.1 User Permission Control
Running as a non-root user:
# Dockerfile security configuration
FROM ubuntu:20.04
# Create a non-privileged user
RUN groupadd -r appuser && useradd -r -g appuser appuser
# Set working directory permissions
WORKDIR /app
COPY app.py .
RUN chown -R appuser:appuser /app
# Switch to the non-privileged user
USER appuser
CMD ["python", "app.py"]
Runtime user control:
# Run with a specific user ID
docker run --user 1000:1000 myapp
# Run with a username
docker run --user appuser myapp
# Verify the current user
docker exec container_id whoami
8.2.2 Filesystem Security
Read-only filesystem:
# Read-only root filesystem
docker run --read-only ubuntu
# Temporary filesystem mount
docker run --read-only --tmpfs /tmp ubuntu
# Read-only mount
docker run -v /host/path:/container/path:ro ubuntu
File permission control:
# Set secure file permissions
COPY --chmod=644 config.yml /app/
COPY --chmod=755 entrypoint.sh /usr/local/bin/
# Remove unnecessary setuid programs
RUN find / -perm /6000 -type f -exec ls -ld {} \; && \
find / -perm /6000 -type f -exec rm {} \;
8.2.3 Capabilities Control
Dropping dangerous capabilities:
# Drop all capabilities
docker run --cap-drop=ALL ubuntu
# Keep only necessary capabilities
docker run --cap-drop=ALL --cap-add=NET_BIND_SERVICE nginx
# View container capabilities
docker run --rm ubuntu capsh --print
Common capabilities explained:
# docker-compose.yml
version: '3.8'
services:
web:
image: nginx
cap_drop:
- ALL
cap_add:
- NET_BIND_SERVICE
- CHOWN
- SETUID
- SETGID
8.3 Image Security
8.3.1 Image Scanning
Using Docker Scout:
# Enable Docker Scout
docker scout quickview
# Scan a local image
docker scout cves nginx:latest
# Scan and get a detailed report
docker scout cves --format sarif nginx:latest > report.sarif
# Compare image security
docker scout compare nginx:1.20 --to nginx:latest
Scanning with Trivy:
# Install Trivy
curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin
# Scan an image for vulnerabilities
trivy image nginx:latest
# Scan for high-severity vulnerabilities
trivy image --severity HIGH,CRITICAL nginx:latest
# Output in JSON format
trivy image --format json --output result.json nginx:latest
8.3.2 Secure Base Images
Choosing a secure base image:
# Use an official slim image
FROM nginx:alpine
# Use a distroless image
FROM gcr.io/distroless/java:11
# Multi-stage build to reduce attack surface
FROM node:16 AS builder
WORKDIR /app
RUN npm ci --only=production
FROM node:16-alpine
WORKDIR /app
COPY --from=builder /app/node_modules ./node_modules
COPY . .
USER node
CMD ["node", "server.js"]
Image hardening:
FROM ubuntu:20.04
# Update the package manager
RUN apt-get update && apt-get upgrade -y
# Remove unnecessary packages
RUN apt-get autoremove -y && apt-get autoclean
# Clean up cache
RUN rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
# Set security configurations
RUN sed -i 's/^PASS_MAX_DAYS.*/PASS_MAX_DAYS 90/' /etc/login.defs
8.4 Network Security
8.4.1 Network Isolation
Custom networks:
# Create an isolated network
docker network create --driver bridge isolated-net
# Run a container on the isolated network
docker run --network isolated-net nginx
# Network communication control
docker network create frontend
docker network create backend
# Frontend container
docker run --name web --network frontend nginx
# Backend container connected to multiple networks
docker run --name api --network backend mysql
docker network connect frontend api
8.4.2 Port Security
Minimizing port exposure:
# Bind only to the local interface
docker run -p 127.0.0.1:8080:80 nginx
# Use a non-standard port
docker run -p 8443:443 nginx
# Dynamic port allocation
docker run -P nginx
Network policies:
# docker-compose.yml
version: '3.8'
services:
web:
image: nginx
networks:
- frontend
ports:
- "127.0.0.1:8080:80"
api:
image: myapi
networks:
- frontend
- backend
expose:
- "3000"
db:
image: postgres
networks:
- backend
environment:
- POSTGRES_PASSWORD_FILE=/run/secrets/db_password
secrets:
- db_password
networks:
frontend:
driver: bridge
backend:
driver: bridge
internal: true
secrets:
db_password:
file: ./db_password.txt
8.5 Secret Management
8.5.1 Docker Secrets
Secret management:
# Create a secret
echo "mypassword" | docker secret create db_password -
# View secrets
docker secret ls
# Use a secret in a service
docker service create --name web --secret db_password nginx
Using secrets in Compose:
version: '3.8'
services:
db:
image: postgres
secrets:
- db_password
- db_user
environment:
- POSTGRES_PASSWORD_FILE=/run/secrets/db_password
- POSTGRES_USER_FILE=/run/secrets/db_user
secrets:
db_password:
external: true
db_user:
external: true
8.5.2 Environment Variable Security
Secure environment variable handling:
# Use a file instead of the command line to pass sensitive information
docker run --env-file .env myapp
# .env file content
DATABASE_URL=postgresql://user:pass@localhost/db
API_KEY=secret_key_here
Runtime secret injection:
FROM alpine
RUN apk add --no-cache curl
COPY entrypoint.sh /
RUN chmod +x /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]
#!/bin/sh
# entrypoint.sh
# Get the secret from an external secret management system
export API_KEY=$(curl -s "$SECRET_SERVICE_URL/api-key")
exec "$@"
8.6 Security Monitoring and Auditing
8.6.1 Container Runtime Monitoring
Monitoring with Falco:
# falco-rules.yaml
- rule: Shell in Container
desc: Detect shell execution in container
condition: spawned_process and container and shell_procs
output: Shell spawned in container (user=%user.name container_id=%container.id image=%container.image.repository:%container.image.tag)
priority: WARNING
- rule: Sensitive File Access
desc: Detect access to sensitive files
condition: open_read and fd.filename in (/etc/passwd, /etc/shadow)
output: Sensitive file accessed (file=%fd.name user=%user.name container=%container.id)
priority: HIGH
Docker Bench Security:
# Run the security benchmark test
docker run --rm --net host --pid host --userns host --cap-add audit_control \
-e DOCKER_CONTENT_TRUST=$DOCKER_CONTENT_TRUST \
-v /etc:/etc:ro \
-v /usr/bin/containerd:/usr/bin/containerd:ro \
-v /usr/bin/runc:/usr/bin/runc:ro \
-v /usr/lib/systemd:/usr/lib/systemd:ro \
-v /var/lib:/var/lib:ro \
-v /var/run/docker.sock:/var/run/docker.sock:ro \
--label docker_bench_security \
docker/docker-bench-security
8.6.2 Log Auditing
Enabling audit logs:
# /etc/docker/daemon.json
{
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3"
},
"experimental": true,
"debug": true
}
Log analysis example:
# View container logs
docker logs --details container_name
# Monitor real-time logs
docker logs -f --tail 100 container_name
# Use journald to collect system logs
journalctl -u docker.service -f
8.7 Best Practices Summary
8.7.1 Development Phase
Dockerfile Best Practices:
# 1. Use an official base image
FROM node:16-alpine
# 2. Set a non-root user
RUN addgroup -g 1001 -S nodejs && \
adduser -S nextjs -u 1001
# 3. Minimize layers
RUN apk add --no-cache libc6-compat && \
apk update
# 4. Set correct permissions when copying files
COPY --chown=nextjs:nodejs package*.json ./
# 5. Switch to the non-privileged user
USER nextjs
# 6. Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD curl -f http://localhost:3000/health || exit 1
# 7. Use COPY instead of ADD
COPY . .
# 8. Explicitly expose ports
EXPOSE 3000
# 9. Use exec form
CMD ["node", "server.js"]
8.7.2 Deployment Phase
Secure Deployment Checklist:
# security-checklist.yml
version: '3.8'
services:
app:
image: myapp:latest
# 1. Use a non-privileged user
user: "1001:1001"
# 2. Read-only root filesystem
read_only: true
# 3. Drop all capabilities
cap_drop:
- ALL
cap_add:
- NET_BIND_SERVICE
# 4. Resource limits
deploy:
resources:
limits:
cpus: '0.5'
memory: 512M
# 5. Health check
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
interval: 30s
timeout: 10s
retries: 3
# 6. Security options
security_opt:
- no-new-privileges:true
# 7. Temporary filesystem
tmpfs:
- /tmp:noexec,nosuid,size=100m
# 8. Read environment variables from a file
env_file:
- .env
# 9. Network isolation
networks:
- app-network
networks:
app-network:
driver: bridge
ipam:
driver: default
config:
- subnet: 172.20.0.0/16
8.7.3 Operations Phase
Continuous security monitoring:
#!/bin/bash
# security-monitor.sh
# 1. Regularly scan images
trivy image --format table myapp:latest
# 2. Check the security status of running containers
docker ps --format "table {{.ID}}\t{{.Image}}\t{{.Status}}" | while read line; do
container_id=$(echo $line | awk '{print $1}')
if [[ $container_id != "CONTAINER" ]]; then
echo "Checking container: $container_id"
docker inspect $container_id | jq '.[] | {Privileged: .HostConfig.Privileged, User: .Config.User}'
fi
done
# 3. Monitor resource usage
docker stats --no-stream --format "table {{.Container}}\t{{.CPUPerc}}\t{{.MemUsage}}"
# 4. Check network connections
netstat -tulpn | grep docker
Practical Cases
Case 1: Web Application Security Hardening
Scenario Description:
Perform comprehensive security hardening for a Node.js web application.
Implementation Steps:
- Secure Dockerfile:
# Multi-stage build
FROM node:16-alpine AS deps
WORKDIR /app
RUN npm ci --only=production && npm cache clean --force
FROM node:16-alpine AS runner
WORKDIR /app
# Create a non-privileged user
RUN addgroup --system --gid 1001 nodejs && \
adduser --system --uid 1001 nextjs
# Copy dependencies
COPY --from=deps --chown=nextjs:nodejs /app/node_modules ./node_modules
COPY --chown=nextjs:nodejs . .
# Set environment variables
ENV NODE_ENV production
# Switch user
USER nextjs
# Expose port
EXPOSE 3000
# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD curl -f http://localhost:3000/health || exit 1
CMD ["node", "server.js"]
- Secure Deployment Configuration:
version: '3.8'
services:
web:
build: .
ports:
- "127.0.0.1:3000:3000"
read_only: true
cap_drop:
- ALL
cap_add:
- NET_BIND_SERVICE
security_opt:
- no-new-privileges:true
tmpfs:
- /tmp:noexec,nosuid,size=50m
environment:
- NODE_ENV=production
secrets:
- jwt_secret
- db_password
networks:
- app-network
deploy:
resources:
limits:
cpus: '1.0'
memory: 512M
reservations:
cpus: '0.5'
memory: 256M
secrets:
jwt_secret:
external: true
db_password:
external: true
networks:
app-network:
driver: bridge
ipam:
driver: default
config:
- subnet: 172.30.0.0/16
- Security Monitoring Script:
#!/bin/bash
# web-security-check.sh
echo "=== Web Application Security Check ==="
# Check container status
echo "1. Container Security Status:"
docker inspect web_container | jq '{
Privileged: .HostConfig.Privileged,
ReadonlyRootfs: .HostConfig.ReadonlyRootfs,
User: .Config.User,
Capabilities: .HostConfig.CapDrop
}'
# Check network connections
echo "2. Network Connections:"
docker exec web_container netstat -tulpn
# Check processes
echo "3. Running Processes:"
docker exec web_container ps aux
# Scan for vulnerabilities
echo "4. Vulnerability Scan:"
trivy image --severity HIGH,CRITICAL web:latest
echo "Security check completed."
Case 2: Microservices Security Architecture
Scenario Description:
Build a secure microservices architecture, including a frontend, API service, and database.
Implementation Plan:
version: '3.8'
services:
# Frontend service
frontend:
image: nginx:alpine
ports:
- "443:443"
- "80:80"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
- ./ssl:/etc/nginx/ssl:ro
networks:
- frontend-net
deploy:
resources:
limits:
memory: 128M
security_opt:
- no-new-privileges:true
read_only: true
tmpfs:
- /var/cache/nginx:noexec,nosuid,size=50m
- /var/run:noexec,nosuid,size=10m
# API Gateway
api-gateway:
image: traefik:v2.9
command:
- --api.insecure=false
- --providers.docker=true
- --entrypoints.websecure.address=:443
- --certificatesresolvers.myresolver.acme.tlschallenge=true
ports:
- "443:443"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
networks:
- frontend-net
- api-net
security_opt:
- no-new-privileges:true
# User service
user-service:
build: ./services/user
networks:
- api-net
- db-net
secrets:
- user_db_password
- jwt_secret
deploy:
resources:
limits:
memory: 256M
security_opt:
- no-new-privileges:true
read_only: true
tmpfs:
- /tmp:noexec,nosuid,size=20m
# Database
database:
image: postgres:14-alpine
networks:
- db-net
volumes:
- db_data:/var/lib/postgresql/data
environment:
- POSTGRES_PASSWORD_FILE=/run/secrets/db_root_password
secrets:
- db_root_password
- user_db_password
security_opt:
- no-new-privileges:true
networks:
frontend-net:
driver: bridge
api-net:
driver: bridge
db-net:
driver: bridge
internal: true
secrets:
jwt_secret:
external: true
db_root_password:
external: true
user_db_password:
external: true
volumes:
db_data:
driver: local
Summary
Docker security is a multi-faceted issue that requires comprehensive protection from multiple angles, including image building, container runtime, network isolation, and secret management. By following security best practices, you can significantly reduce the security risks of containerized applications:
- Principle of Least Privilege: Use non-privileged users and drop unnecessary capabilities.
- Defense in Depth: Multi-layered security controls, network isolation, and access control.
- Continuous Monitoring: Real-time monitoring of container behavior and regular security scanning.
- Secret Management: Use dedicated secret management services and avoid hardcoding.
- Image Security: Use trusted image sources, and regularly update and scan images.
Remember, container security is not a one-time job, but a process that requires continuous attention and improvement throughout the application lifecycle.