Security Best Practices
Securing Docker containers is essential for protecting your applications, data, and infrastructure. This guide covers comprehensive security best practices across the container lifecycle — from building images to running containers in production.
Security Layers
Docker security operates at multiple layers:
┌─────────────────────────────────────────────┐
│ Application Security │
│ (Code, Dependencies, Secrets Management) │
├─────────────────────────────────────────────┤
│ Container Security │
│ (User, Capabilities, Read-only FS) │
├─────────────────────────────────────────────┤
│ Image Security │
│ (Base Image, Scanning, Signing) │
├─────────────────────────────────────────────┤
│ Docker Daemon Security │
│ (TLS, Authorization, Rootless Mode) │
├─────────────────────────────────────────────┤
│ Host Security │
│ (Kernel, Namespaces, Cgroups, SELinux) │
└─────────────────────────────────────────────┘Image Security
Use Trusted Base Images
dockerfile
# ✅ Good: Use official images from verified publishers
FROM node:20-alpine
# ✅ Better: Pin to a specific digest for immutability
FROM node:20-alpine@sha256:abc123def456...
# ❌ Bad: Use unverified third-party images
FROM randomuser/node:latestScan Images for Vulnerabilities
bash
# Scan with Docker Scout
docker scout cves my-image:latest
# Scan with Docker Scout and get recommendations
docker scout recommendations my-image:latest
# Scan with Trivy (open-source)
trivy image my-image:latest
# Scan with Snyk
snyk container test my-image:latest
# Scan during CI/CD pipeline
docker scout cves --exit-code --only-severity critical,high my-image:latestMinimize Image Attack Surface
dockerfile
# Use minimal base images
FROM alpine:3.19
# Remove unnecessary packages
RUN apk add --no-cache \
nodejs \
npm && \
rm -rf /var/cache/apk/*
# Use distroless for maximum security
FROM gcr.io/distroless/nodejs20-debian12
# Use scratch for statically compiled binaries
FROM scratch
COPY --from=builder /app/binary /binary
CMD ["/binary"]Image Signing and Verification
bash
# Enable Docker Content Trust
export DOCKER_CONTENT_TRUST=1
# Push a signed image
docker push myuser/myapp:1.0
# Pull only signed images
docker pull myuser/myapp:1.0
# Verify image with Cosign (Sigstore)
cosign sign --key cosign.key myregistry/myapp:1.0
cosign verify --key cosign.pub myregistry/myapp:1.0Container Runtime Security
Run as Non-Root User
dockerfile
# Create and use a non-root user
FROM node:20-alpine
# Create a system group and user
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
# Set ownership of application files
WORKDIR /app
COPY --chown=appuser:appgroup . .
RUN npm ci --only=production
# Switch to non-root user
USER appuser
CMD ["node", "server.js"]bash
# Verify the container is running as non-root
docker run --rm my-app:latest whoami
# Output: appuser
# Force a container to run as a specific user
docker run -u 1000:1000 my-app:latestUse Read-Only Filesystems
bash
# Run with read-only root filesystem
docker run -d \
--read-only \
--tmpfs /tmp \
--tmpfs /var/run \
my-app:latest
# In Docker Compose
# services:
# app:
# read_only: true
# tmpfs:
# - /tmp
# - /var/runDrop Linux Capabilities
bash
# Drop all capabilities and add only needed ones
docker run -d \
--cap-drop ALL \
--cap-add NET_BIND_SERVICE \
my-app:latestCommon Linux Capabilities
| Capability | Description | Usually Needed? |
|---|---|---|
NET_BIND_SERVICE | Bind to ports below 1024 | Sometimes |
CHOWN | Make arbitrary changes to file UIDs/GIDs | Rarely |
DAC_OVERRIDE | Bypass file permission checks | Rarely |
SETUID / SETGID | Set user/group ID | Rarely |
SYS_ADMIN | Broad admin privileges | ❌ Never |
SYS_PTRACE | Trace processes | Debugging only |
NET_RAW | Use raw sockets | Rarely |
Security Options
bash
# Disable privilege escalation
docker run -d --security-opt no-new-privileges my-app:latest
# Apply AppArmor profile
docker run -d --security-opt apparmor=docker-default my-app:latest
# Apply Seccomp profile
docker run -d --security-opt seccomp=/path/to/profile.json my-app:latest
# Apply SELinux labels
docker run -d --security-opt label=type:container_runtime_t my-app:latestPrevent Privilege Escalation
bash
# Never use --privileged unless absolutely necessary
# ❌ Bad: Gives full host access
docker run --privileged my-app:latest
# ✅ Good: Use specific capabilities instead
docker run --cap-drop ALL --cap-add NET_BIND_SERVICE my-app:latestSecrets Management
Docker Secrets (Swarm Mode)
bash
# Create a secret
echo "my-secret-password" | docker secret create db_password -
# Create from a file
docker secret create ssl_cert ./server.crt
# Use secrets in a service
docker service create \
--name my-db \
--secret db_password \
-e POSTGRES_PASSWORD_FILE=/run/secrets/db_password \
postgres:16Environment Variable Best Practices
bash
# ❌ Bad: Secrets in Dockerfile
ENV DATABASE_PASSWORD=mysecretpassword
# ❌ Bad: Secrets in docker-compose.yml
environment:
- DATABASE_PASSWORD=mysecretpassword
# ✅ Good: Use .env file (not committed to Git)
env_file:
- .env
# ✅ Good: Use Docker secrets
secrets:
db_password:
file: ./secrets/db_password.txt
# ✅ Good: Use external secret management
# HashiCorp Vault, AWS Secrets Manager, etc.Build-Time Secrets with BuildKit
dockerfile
# syntax=docker/dockerfile:1
FROM node:20-alpine
# Use BuildKit secret mount (secret not stored in image layers)
RUN --mount=type=secret,id=npmrc,target=/root/.npmrc \
npm ci --only=productionbash
# Build with the secret
docker build --secret id=npmrc,src=.npmrc -t my-app:latest .Network Security
Isolate Networks
yaml
# docker-compose.yml with network isolation
services:
frontend:
networks:
- frontend-net
api:
networks:
- frontend-net
- backend-net
database:
networks:
- backend-net # Not accessible from frontend
networks:
frontend-net:
backend-net:
internal: true # No external accessLimit Network Exposure
bash
# Bind to localhost only
docker run -d -p 127.0.0.1:8080:80 my-app:latest
# Disable inter-container communication
docker network create --driver bridge \
-o com.docker.network.bridge.enable_icc=false \
isolated-netResource Limits
Prevent containers from consuming excessive resources:
bash
# Set memory limits
docker run -d \
--memory=512m \
--memory-swap=1g \
my-app:latest
# Set CPU limits
docker run -d \
--cpus=1.5 \
--cpu-shares=512 \
my-app:latest
# Set process limits
docker run -d --pids-limit=100 my-app:latest
# Prevent fork bombs
docker run -d --ulimit nproc=50:100 my-app:latest
# Limit open files
docker run -d --ulimit nofile=1024:2048 my-app:latestResource Limits in Compose
yaml
services:
app:
image: my-app:latest
deploy:
resources:
limits:
cpus: '1.0'
memory: 512M
reservations:
cpus: '0.25'
memory: 128MDocker Daemon Security
Enable TLS for Remote Access
bash
# Generate TLS certificates
openssl genrsa -aes256 -out ca-key.pem 4096
openssl req -new -x509 -days 365 -key ca-key.pem -out ca.pem
# Configure daemon with TLS
# /etc/docker/daemon.json
{
"tls": true,
"tlscacert": "/etc/docker/certs/ca.pem",
"tlscert": "/etc/docker/certs/server-cert.pem",
"tlskey": "/etc/docker/certs/server-key.pem",
"hosts": ["unix:///var/run/docker.sock", "tcp://0.0.0.0:2376"]
}Rootless Mode
bash
# Install rootless Docker
dockerd-rootless-setuptool.sh install
# Set environment variables
export PATH=/home/user/bin:$PATH
export DOCKER_HOST=unix:///run/user/1000/docker.sock
# Verify rootless mode
docker info | grep -i rootlessSecurity Checklist
| Category | Practice | Priority |
|---|---|---|
| Image | Use minimal base images | 🔴 Critical |
| Image | Scan images for vulnerabilities | 🔴 Critical |
| Image | Pin image versions/digests | 🟡 High |
| Image | Use multi-stage builds | 🟡 High |
| Runtime | Run as non-root user | 🔴 Critical |
| Runtime | Use read-only filesystem | 🟡 High |
| Runtime | Drop all capabilities, add specific | 🟡 High |
| Runtime | Set no-new-privileges | 🟡 High |
| Runtime | Never use --privileged | 🔴 Critical |
| Secrets | Never store secrets in images | 🔴 Critical |
| Secrets | Use secret management tools | 🟡 High |
| Network | Isolate networks by tier | 🟡 High |
| Network | Bind to localhost when possible | 🟡 High |
| Resources | Set memory and CPU limits | 🟡 High |
| Daemon | Enable TLS for remote access | 🟡 High |
| Daemon | Consider rootless mode | 🟢 Medium |
| Logging | Enable audit logging | 🟢 Medium |
Next Steps
- Image Building Best Practices — Build secure, minimal images
- Docker Networking Guide — Secure container networking
- Storage Management — Protect persistent data
- Configuration Management — Configure Docker daemon securely
- Docker Engine Architecture — Understand Docker security internals