Docker containers share the host kernel, which means a misconfigured container can compromise the entire server. The CIS Docker Benchmark provides industry-standard recommendations, but its 200+ pages can be overwhelming. This checklist distills the most critical security controls into 50 actionable items organized by category, each with verification commands and remediation steps.

Score yourself: each item is worth 2 points for a total of 100. A score below 60 indicates significant risk exposure. Between 60 and 80 is acceptable for development environments. Production environments should target 80+.

Section 1: Host Configuration (10 points)

1. Use a dedicated Docker host

Run Docker on a dedicated server or VM, not a shared general-purpose machine. This limits the blast radius of a container escape.

# Verify: check for non-Docker services
systemctl list-units --type=service --state=running | grep -v docker
# Fewer services = better isolation

2. Keep the host kernel updated

Container escapes frequently exploit kernel vulnerabilities. Kernel updates are your first line of defense.

# Check kernel version
uname -r
# Compare against latest stable for your distribution
# Automate updates:
# Ubuntu/Debian: unattended-upgrades
# RHEL/CentOS: dnf-automatic

3. Harden the host operating system

Apply CIS benchmarks for your base OS. Disable unnecessary services, configure firewall rules, and set restrictive file permissions.

# Verify SSH hardening
grep -E "^(PermitRootLogin|PasswordAuthentication|X11Forwarding)" /etc/ssh/sshd_config
# Expected:
# PermitRootLogin no
# PasswordAuthentication no
# X11Forwarding no

4. Use a separate partition for Docker data

Prevent container storage from filling the root filesystem:

# Check Docker data location
docker info --format '{{.DockerRootDir}}'
# Default: /var/lib/docker

# Verify it's on a separate mount
df -h /var/lib/docker
# Should show a dedicated partition, not /

5. Audit Docker files and directories

Enable auditd rules for Docker-related files:

# Add to /etc/audit/rules.d/docker.rules
-w /usr/bin/docker -p wa -k docker
-w /var/lib/docker -p wa -k docker
-w /etc/docker -p wa -k docker
-w /usr/lib/systemd/system/docker.service -p wa -k docker
-w /etc/docker/daemon.json -p wa -k docker
-w /etc/default/docker -p wa -k docker

Section 2: Docker Daemon Configuration (10 points)

6. Restrict network traffic between containers

# In /etc/docker/daemon.json
{
  "icc": false
}
# Verify:
docker network inspect bridge --format '{{.Options}}'
# com.docker.network.bridge.enable_icc should be false

7. Set the logging level to info

# In /etc/docker/daemon.json
{
  "log-level": "info"
}
# Avoid "debug" in production (leaks sensitive data)

8. Enable user namespace remapping

Map container root to a non-root host UID:

# In /etc/docker/daemon.json
{
  "userns-remap": "default"
}
# Verify:
docker info --format '{{.SecurityOptions}}'
# Should include "name=userns"

9. Use a non-default storage driver

# overlay2 is recommended for most distributions
docker info --format '{{.Driver}}'
# Expected: overlay2
# Avoid: aufs, devicemapper (deprecated)

10. Configure default ulimits

# In /etc/docker/daemon.json
{
  "default-ulimits": {
    "nofile": { "Name": "nofile", "Hard": 65536, "Soft": 65536 },
    "nproc": { "Name": "nproc", "Hard": 4096, "Soft": 4096 }
  }
}

Section 3: Image Security (10 points)

11. Use trusted base images only

Pull from Docker Official Images or verified publisher images. Never use unverified community images in production.

# Good: official images
FROM python:3.12-slim
FROM node:20-alpine
FROM postgres:16-alpine

# Bad: unverified random images
FROM randomuser/mybase:latest

12. Enable Docker Content Trust

# Enable image signature verification
export DOCKER_CONTENT_TRUST=1

# Verify signed images only
docker pull library/alpine:3.19
# Will fail if the image is not signed

13. Scan images for vulnerabilities

# Using Trivy (integrated with usulnet)
trivy image myapp:latest

# Using Docker Scout
docker scout cves myapp:latest

# Fail builds on critical vulnerabilities
trivy image --exit-code 1 --severity CRITICAL myapp:latest

14. Use minimal base images

Smaller images have fewer vulnerabilities:

Base Image Size Packages CVE Surface
ubuntu:24.04 78 MB ~100 High
debian:bookworm-slim 52 MB ~80 Medium
alpine:3.20 7 MB ~15 Low
distroless (gcr.io) 2-20 MB ~5 Minimal
scratch 0 MB 0 None

15. Do not store secrets in images

# BAD: secrets baked into image layers
COPY .env /app/.env
ENV DB_PASSWORD=s3cur3_p@ss

# GOOD: secrets injected at runtime
# Use Docker secrets, environment variables, or vault integration
Warning: Even if you delete a file in a later Dockerfile layer, it remains accessible in the image layer history. Use multi-stage builds to ensure secrets never appear in the final image.

Section 4: Container Runtime Security (10 points)

16. Run containers as non-root

# In Dockerfile
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
USER appuser

# Verify running containers
docker inspect --format '{{.Config.User}}' mycontainer
# Should NOT be empty or "root" or "0"

17. Drop all capabilities and add only what's needed

docker run --cap-drop ALL --cap-add NET_BIND_SERVICE myapp

# In Compose
security_opt:
  - no-new-privileges:true
cap_drop:
  - ALL
cap_add:
  - NET_BIND_SERVICE

18. Set read-only root filesystem

docker run --read-only \
  --tmpfs /tmp:rw,noexec,nosuid \
  --tmpfs /var/run:rw,noexec,nosuid \
  myapp

19. Set memory and CPU limits

docker run \
  --memory 512m \
  --memory-swap 512m \
  --cpus 1.0 \
  --pids-limit 100 \
  myapp

# Verify limits
docker stats --no-stream --format "table {{.Name}}\t{{.MemUsage}}\t{{.MemPerc}}\t{{.CPUPerc}}"

20. Enable no-new-privileges

docker run --security-opt no-new-privileges myapp

# Verify
docker inspect --format '{{.HostConfig.SecurityOpt}}' mycontainer
# Should contain "no-new-privileges"

Section 5: Network Security (10 points)

21. Do not use the default bridge network

# Create custom networks
docker network create --driver bridge app-network

# Use in Compose
networks:
  app-network:
    driver: bridge

22. Restrict container-to-container communication

# Disable ICC on the default bridge
# /etc/docker/daemon.json
{ "icc": false }

# Use explicit network connections
docker network connect app-network container-a
docker network connect app-network container-b

23. Do not map privileged ports unnecessarily

# BAD: exposing on all interfaces
docker run -p 80:80 myapp

# BETTER: bind to specific interface
docker run -p 127.0.0.1:80:80 myapp

# BEST: use a reverse proxy
docker run --network app-net myapp
# Expose only through nginx/traefik reverse proxy

24. Use internal networks for backend services

# In Compose
networks:
  frontend:
    driver: bridge
  backend:
    driver: bridge
    internal: true  # No outbound internet access

25. Configure Docker's iptables integration

# Verify Docker iptables rules
iptables -L DOCKER -n
iptables -L DOCKER-USER -n

# Add custom rules to DOCKER-USER chain (survives Docker restarts)
iptables -I DOCKER-USER -s 10.0.0.0/8 -j ACCEPT
iptables -I DOCKER-USER -j DROP

Section 6: Secrets and Data Management (10 points)

26. Use Docker secrets instead of environment variables

# Docker Swarm secrets
echo "mypassword" | docker secret create db_pass -

# In Compose (Swarm mode)
services:
  db:
    secrets:
      - db_pass
secrets:
  db_pass:
    external: true

27. Encrypt data at rest

Use encrypted storage drivers or filesystem-level encryption for Docker volumes containing sensitive data.

28. Use tmpfs for sensitive runtime data

docker run --tmpfs /run/secrets:rw,noexec,nosuid,size=1m myapp

29. Rotate secrets regularly

Implement automated secret rotation. In Swarm, create a new secret version and update services.

30. Protect the Docker socket

# Verify socket permissions
ls -la /var/run/docker.sock
# Expected: srw-rw---- root docker

# NEVER mount the Docker socket into containers unless absolutely necessary
# If required, use read-only mount and a socket proxy:
docker run -v /var/run/docker.sock:/var/run/docker.sock:ro \
  tecnativa/docker-socket-proxy

Section 7: Logging and Monitoring (10 points)

31. Configure centralized logging

# In /etc/docker/daemon.json
{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "10m",
    "max-file": "3",
    "labels": "service,environment"
  }
}

32. Enable container event monitoring

# Monitor Docker events
docker events --filter 'type=container' \
  --format '{{.Time}} {{.Action}} {{.Actor.Attributes.name}}'

# Forward to SIEM/log aggregator for alerting

33. Monitor container resource usage

# Real-time monitoring
docker stats --format "table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}\t{{.NetIO}}"

# Use Prometheus + cAdvisor for historical data
# Or use usulnet's built-in monitoring dashboard

34. Set up alerting for security events

Monitor for: privileged container starts, containers running as root, new image pulls, container escape attempts, and unexpected network connections.

35. Implement runtime security monitoring

# Use Falco for runtime threat detection
docker run -d --name falco \
  --privileged \
  -v /var/run/docker.sock:/var/run/docker.sock \
  -v /dev:/host/dev \
  -v /proc:/host/proc:ro \
  falcosecurity/falco

Section 8: Orchestration Security (10 points)

36. Encrypt Swarm overlay network traffic

docker network create --opt encrypted --driver overlay secure-net

37. Rotate Swarm join tokens regularly

# Rotate worker join token
docker swarm join-token --rotate worker

# Rotate manager join token
docker swarm join-token --rotate manager

38. Enable mutual TLS for Swarm communication

Swarm enables mTLS by default. Verify the certificate rotation period:

docker info --format '{{.Swarm.Cluster.Spec.CAConfig.NodeCertExpiry}}'
# Default: 90 days. Recommended: 30 days or less

39. Limit manager node count

Use 3 or 5 manager nodes for fault tolerance. More managers increase the attack surface and reduce Raft consensus performance.

40. Use placement constraints for sensitive workloads

docker service create \
  --constraint 'node.labels.security-zone==restricted' \
  --constraint 'node.role==worker' \
  sensitive-app

Section 9: Supply Chain Security (10 points)

41. Pin image digests in production

# Instead of tags (mutable)
FROM nginx:1.27

# Use digests (immutable)
FROM nginx@sha256:abc123...

# Get digest
docker inspect --format '{{.RepoDigests}}' nginx:1.27

42. Use multi-stage builds

FROM golang:1.22 AS builder
WORKDIR /app
COPY . .
RUN CGO_ENABLED=0 go build -o /server .

FROM gcr.io/distroless/static:nonroot
COPY --from=builder /server /server
ENTRYPOINT ["/server"]

43. Implement image signing

# Using Cosign
cosign sign --key cosign.key myregistry.com/myapp:v1.0.0

# Verify before deployment
cosign verify --key cosign.pub myregistry.com/myapp:v1.0.0

44. Scan dependencies in CI/CD

# GitHub Actions example
- name: Scan image
  uses: aquasecurity/trivy-action@master
  with:
    image-ref: myapp:${{ github.sha }}
    format: sarif
    output: trivy-results.sarif
    severity: CRITICAL,HIGH

45. Maintain a private registry

Run your own registry to control exactly which images are available in your environment:

docker run -d -p 5000:5000 \
  --name registry \
  -v /data/registry:/var/lib/registry \
  -e REGISTRY_AUTH=htpasswd \
  -e REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd \
  registry:2

Section 10: Compliance and Governance (10 points)

46. Run the CIS Docker Benchmark

# Docker Bench Security (automated CIS checks)
docker run --rm --net host --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 /usr/lib/systemd:/usr/lib/systemd:ro \
  -v /etc:/etc:ro \
  docker/docker-bench-security

47. Document your container security policy

Maintain a written policy covering: approved base images, required security scanning thresholds, resource limit requirements, network policies, and incident response procedures.

48. Implement change management for production images

Require pull request approvals for Dockerfile changes. Enforce automated security scans in CI before merging.

49. Conduct regular security audits

Schedule quarterly reviews of: running container configurations, image vulnerability scan results, access control lists, network policies, and secret rotation compliance.

50. Maintain an inventory of all running containers

Know exactly what is running in your environment at all times. Tools like usulnet provide automatic container inventory across all nodes with security metadata.

Tip: Use this checklist as a recurring audit tool. Run through it quarterly for production environments and after any major infrastructure change. Track your score over time to measure security posture improvement.

Scoring Summary

Score Range Rating Recommended Action
90-100 Excellent Maintain current posture, review quarterly
80-89 Good Address remaining gaps within 30 days
60-79 Acceptable (dev only) Prioritize critical items, create remediation plan
40-59 Poor Immediate attention required for production systems
0-39 Critical Stop deploying until core security controls are in place

Remember: Security is not a one-time configuration. Containers, images, and threats evolve continuously. Automate as many checks as possible and integrate security scanning into your CI/CD pipeline to catch issues before they reach production.