Docker Security Best Practices
Docker containers provide isolated environments for applications, but security should be a primary concern when building and deploying containers. This guide covers essential security practices to keep your Docker deployments safe and secure.
Understanding Docker Security
Docker security involves multiple layers: the host system, the Docker daemon, containers, and the applications running inside them. Each layer requires proper security configuration.
1. Use Minimal Base Images
Small Attack Surface
Choose minimal base images to reduce the potential attack surface:
# Good: Use Alpine Linux
FROM node:18-alpine
# Better: Use distroless images
FROM gcr.io/distroless/nodejs18-debian11
# Best: Use scratch for compiled applications
FROM scratchOfficial vs Community Images
# Prefer official images
FROM nginx:alpine
FROM postgres:15-alpine
FROM redis:7-alpine
# Avoid unverified community images
# FROM randomuser/nginx:latest # ❌ Avoid this2. Run as Non-Root User
Create Dedicated User
FROM node:18-alpine
# Create non-root user and group
RUN addgroup -g 1001 -S nodejs && \
adduser -S nextjs -u 1001
WORKDIR /app
# Copy files and set ownership
COPY --chown=nextjs:nodejs . .
USER nextjs
EXPOSE 3000
CMD ["node", "server.js"]Multi-Stage with User
# Build stage
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
RUN npm run build
# Production stage
FROM node:18-alpine AS production
# Create user
RUN addgroup -g 1001 -S nodejs && \
adduser -S nodejs -u 1001
WORKDIR /app
# Copy with correct ownership
COPY --from=builder --chown=nodejs:nodejs /app/dist ./dist
COPY --from=builder --chown=nodejs:nodejs /app/node_modules ./node_modules
COPY --from=builder --chown=nodejs:nodejs /app/package.json ./package.json
USER nodejs
CMD ["node", "dist/index.js"]3. Secrets Management
Never Hardcode Secrets
# ❌ BAD: Hardcoded secrets
ENV DATABASE_PASSWORD "mypassword123"
ENV API_KEY "sk-1234567890abcdef"
# ✅ GOOD: Use environment files or Docker secrets
COPY .env.example .env
# Load .env file with actual secrets at runtimeUsing Docker Secrets
# docker-compose.yml
version: '3.8'
services:
app:
build: .
secrets:
- db_password
- api_key
environment:
- DB_PASSWORD_FILE=/run/secrets/db_password
- API_KEY_FILE=/run/secrets/api_key
secrets:
db_password:
external: true
api_key:
external: trueEnvironment Variables in Dockerfile
# Use ARG for build-time variables
ARG BUILD_DATE
ARG VCS_REF
# Use ENV for runtime variables
ENV NODE_ENV=production
ENV PORT=3000
# Default values can be overridden
ENV APP_VERSION=${VCS_REF:-unknown}4. File System Security
Read-Only File System
FROM node:18-alpine
# Create app directory
RUN mkdir -p /app && chown -R 1001:1001 /app
WORKDIR /app
# Copy application
COPY --chown=1001:1001 . .
# Create non-root user
RUN adduser -D -s /bin/sh -u 1001 appuser
USER appuser
# Use read-only root filesystem
EXPOSE 3000
CMD ["node", "server.js"]Docker Compose with Read-Only Root
version: '3.8'
services:
app:
build: .
read_only: true
tmpfs:
- /tmp
- /run
volumes:
- ./logs:/app/logs:rwProper File Permissions
FROM python:3.11-slim
# Create directories with proper permissions
RUN mkdir -p /app/logs /app/uploads && \
groupadd -r appgroup && \
useradd -r -g appgroup appuser && \
chown -R appuser:appgroup /app
WORKDIR /app
COPY --chown=appuser:appgroup requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY --chown=appuser:appgroup . .
USER appuser
CMD ["python", "app.py"]5. Network Security
Expose Only Necessary Ports
FROM nginx:alpine
# Only expose required ports
EXPOSE 80
# Don't expose port 443 if not using HTTPSDocker Compose Network Isolation
version: '3.8'
services:
web:
build: ./web
networks:
- frontend
- backend
expose:
- "3000"
database:
image: postgres:15-alpine
networks:
- backend
# No expose - only accessible via backend network
nginx:
image: nginx:alpine
ports:
- "80:80"
- "443:443"
networks:
- frontend
networks:
frontend:
driver: bridge
backend:
driver: bridge
internal: true # No external access6. Resource Limits
Set Resource Constraints
version: '3.8'
services:
app:
build: .
deploy:
resources:
limits:
cpus: '0.5'
memory: 512M
reservations:
cpus: '0.25'
memory: 256MDocker Run with Limits
# Limit CPU usage
docker run --cpus="0.5" myapp
# Limit memory usage
docker run --memory="512m" myapp
# Set swap limit
docker run --memory="512m" --memory-swap="1g" myapp
# Limit disk I/O
docker run --device-read-bps=/dev/sda:1mb myapp
docker run --device-write-bps=/dev/sda:1mb myapp7. Health Checks
Implement Health Checks
FROM node:18-alpine
# Install curl for health checks
RUN apk add --no-cache curl
WORKDIR /app
COPY . .
RUN npm ci --only=production
# Add health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD curl -f http://localhost:3000/health || exit 1
EXPOSE 3000
CMD ["node", "server.js"]Custom Health Check Script
FROM python:3.11-slim
WORKDIR /app
# Create health check script
RUN echo '#!/bin/bash\n\
curl -f http://localhost:8000/health || exit 1' > /healthcheck.sh && \
chmod +x /healthcheck.sh
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD ["/healthcheck.sh"]
EXPOSE 8000
CMD ["python", "app.py"]8. Image Scanning and Updates
Using Docker Scout
# Scan image for vulnerabilities
docker scout cves myapp:latest
# Scan during build
docker build --provenance=true --sbom=true -t myapp:latest .
# View detailed scan results
docker scout quickview myapp:latestTrivy Security Scanner
# Install Trivy
sudo apt-get install wget apt-transport-https gnupg lsb-release
wget -qO - https://aquasecurity.github.io/trivy-repo/deb/public.key | sudo apt-key add -
echo "deb https://aquasecurity.github.io/trivy-repo/deb $(lsb_release -sc) main" | sudo tee -a /etc/apt/sources.list.d/trivy.list
sudo apt-get update
sudo apt-get install trivy
# Scan image
trivy image myapp:latest
# Scan in CI/CD
trivy image --exit-code 0 --severity HIGH,CRITICAL myapp:latestAutomated Security Updates
# .github/workflows/security.yml
name: Security Scan
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
security:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@master
with:
image-ref: 'myapp:latest'
format: 'sarif'
output: 'trivy-results.sarif'
- name: Upload Trivy scan results
uses: github/codeql-action/upload-sarif@v2
with:
sarif_file: 'trivy-results.sarif'9. Runtime Security
AppArmor/SELinux Profiles
# Enable AppArmor for Docker
sudo systemctl enable apparmor
sudo systemctl start apparmor
# Load default Docker profile
sudo apparmor_parser -r /etc/apparmor.d/docker-default
# Use AppArmor in docker-compose
version: '3.8'
services:
app:
build: .
security_opt:
- apparmor:docker-defaultSeccomp Security Profiles
# Use default seccomp profile in Docker daemon
# Or specify custom profile
# In docker-compose.yml
version: '3.8'
services:
app:
build: .
security_opt:
- seccomp:./seccomp-profile.jsonNo New Privileges
version: '3.8'
services:
app:
build: .
security_opt:
- no-new-privileges:true
user: "1001:1001"10. Docker Daemon Security
Secure Docker Daemon
# Edit /etc/docker/daemon.json
{
"live-restore": true,
"userland-proxy": false,
"no-new-privileges": true,
"seccomp-profile": "/etc/docker/seccomp/default.json",
"experimental": false,
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3"
}
}Use TLS for Remote Docker
# Generate TLS certificates
docker --tlsverify --tlscacert=ca.pem --tlscert=cert.pem --tlskey=key.pem -H=tcp://hostname:2376 version
# Enable TLS in daemon.json
{
"tls": true,
"tlscacert": "/etc/docker/certs.d/ca.pem",
"tlscert": "/etc/docker/certs.d/server-cert.pem",
"tlskey": "/etc/docker/certs.d/server-key.pem",
"tlsverify": true
}11. Container Runtime Security
Drop Capabilities
version: '3.8'
services:
app:
build: .
cap_drop:
- ALL
cap_add:
- CHOWN
- SETGID
- SETUIDRemove SUID/SGID Binaries
FROM alpine:latest
# Remove SUID/SGID binaries
RUN find / -perm /6000 -type f -exec chmod a-s {} \; || true
# Install only necessary packages
RUN apk add --no-cache \
ca-certificates \
&& rm -rf /var/cache/apk/*12. Logging and Monitoring
Centralized Logging
version: '3.8'
services:
app:
build: .
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
labels: "environment,service"
elasticsearch:
image: docker.elastic.co/elasticsearch/elasticsearch:8.5.0
environment:
- discovery.type=single-node
- "ES_JAVA_OPTS=-Xms512m -Xmx512m"
logstash:
image: docker.elastic.co/logstash/logstash:8.5.0
volumes:
- ./logstash.conf:/usr/share/logstash/pipeline/logstash.conf
kibana:
image: docker.elastic.co/kibana/kibana:8.5.0
ports:
- "5601:5601"Audit Docker Events
# Monitor Docker events
docker events --filter event=start
# Log Docker events to file
docker events --format '{{.Time}} {{.Status}} {{.Actor.Attributes.name}}' >> docker-events.log &
# Monitor resource usage
docker stats --no-stream --format "table {{.Container}}\t{{.CPUPerc}}\t{{.MemUsage}}"13. .dockerignore File
Exclude Sensitive Files
# Version control
.git
.gitignore
# Environment files
.env
.env.local
.env.production
# Secrets and keys
*.pem
*.key
*.p12
secrets/
# Development files
node_modules
.vscode
.idea
# Build artifacts
dist
build
coverage
# Logs
*.log
logs/
# OS files
.DS_Store
Thumbs.db
# Temporary files
*.tmp
*.temp
.cacheSecurity Checklist
Build Phase
- Use minimal base images
- Create non-root user
- Don’t include secrets in images
- Set proper file permissions
- Use .dockerignore
- Sign images with Docker Content Trust
Runtime Phase
- Use read-only filesystems
- Set resource limits
- Use custom networks
- Drop unnecessary capabilities
- Enable health checks
- Use security profiles
Monitoring Phase
- Scan images regularly
- Monitor running containers
- Log security events
- Update base images
- Audit Docker daemon
- Review access controls
Tools and Resources
Security Tools
- Docker Scout - Built-in vulnerability scanner
- Trivy - Open-source vulnerability scanner
- Clair - Static analysis for vulnerabilities
- Anchore Engine - Container inspection and analysis
Security Standards
- CIS Docker Benchmark - Security best practices
- NIST Container Security Guide - Official security guidelines
External Resources:
Related Tutorials:
Last updated on