Introduction
Docker containers have revolutionized application deployment, but they also introduce security considerations that differ from traditional server deployments. Proper Docker security protects your applications from common threats while maintaining the benefits containerization provides.
This guide covers practical security measures you can implement immediately. The recommendations target real attack vectors and include implementation steps. We focus on measures with meaningful security returns without creating operational burdens that lead administrators to disable protections.
Container security spans three layers: images, runtimes, and infrastructure. We will cover all three layers, creating defense in depth that protects your applications even if one layer fails.
Image Security
Container images form the foundation of your deployments. Securing images prevents vulnerable code from reaching production and reduces your attack surface.
Base Image Selection
Choose minimal base images that contain only necessary components. Smaller images have fewer packages with potential vulnerabilities:
# Use Alpine or distroless images for production
FROM node:20-alpine AS builder
# Or for production, use distroless
FROM gcr.io/distroless/nodejs20-debian12 AS production
Alpine Linux images are significantly smaller than full distributions, reducing both storage requirements and potential vulnerability surfaces. For applications where security is paramount, Googleβs distroless images provide even smaller attack surfaces by omitting shells and package managers.
Pin image versions to specific tags rather than using latest. This ensures reproducible builds and prevents unexpected changes:
# Pin to specific version
FROM nginx:1.25-alpine
# Avoid this pattern in production
# FROM nginx:latest
Image Scanning
Automated vulnerability scanning catches security issues before they reach production. Integrate scanning into your build pipeline:
# Scan local image with Trivy
docker run --rm \
-v /var/run/docker.sock:/var/run/docker.sock \
aquasec/trivy:latest \
image nginx:1.25-alpine
# Scan during CI/CD with GitHub Actions
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@master
with:
image-ref: 'nginx:1.25-alpine'
format: 'table'
exit-code: '1'
severity: 'CRITICAL,HIGH'
Configure your scanner to fail builds on critical vulnerabilities. This prevents insecure images from reaching your registry:
# GitHub Actions workflow excerpt
- name: Vulnerability scan
uses: aquasecurity/trivy-action@master
with:
image-ref: 'myregistry/myapp:${{ github.sha }}'
format: 'sarif'
output: 'trivy-results.sarif'
exit-code: '1'
severity: 'CRITICAL,HIGH'
Build Secrets
Never bake secrets into images. Build arguments and multi-stage builds protect sensitive information:
# Build stage with access to secrets
FROM node:20-alpine AS builder
# Copy only package files first (caching)
COPY package*.json ./
RUN npm ci --only=production
# Copy application code
COPY . .
# Build production assets
RUN npm run build
# Production stage without build secrets
FROM node:20-alpine AS production
COPY --from=builder /app/dist /app
COPY --from=builder /app/node_modules /app/node_modules
CMD ["node", "app/index.js"]
This pattern ensures build-time secrets never appear in the final image. Dependencies install in a separate stage, and only production artifacts copy to the final image.
Container Runtime Security
Runtime security protects running containers from exploitation. Even if images contain vulnerabilities, proper runtime configuration limits the impact of potential exploits.
User Permissions
Containers run as root by default, which presents security risks. Create dedicated users for your applications:
# Create application user
RUN addgroup -g 1000 appgroup && \
adduser -u 1000 -G appgroup -s /bin/sh -D appuser
# Switch to unprivileged user
USER appuser
Build images that include application users. Run containers with the --user flag when possible:
# Run container as specific user
docker run --user 1000:1000 myapp:latest
Capability Restrictions
Linux capabilities grant specific privileges beyond what normal users have. Limit capabilities to only those required:
# Run container with minimal capabilities
docker run \
--cap-drop ALL \
--cap-add NET_BIND_SERVICE \
myapp:latest
The --cap-drop ALL removes all capabilities, and --cap-add adds only necessary ones. Your application likely needs very few additional capabilities beyond basic operation.
Read-Only Filesystems
Prevent containers from modifying their filesystem by mounting it read-only:
# Run container with read-only filesystem
docker run --read-only myapp:latest
For applications that require temporary file storage, use tmpfs mounts:
docker run \
--read-only \
--tmpfs /tmp:rw,size=100m,mode=1777 \
myapp:latest
Resource Limits
Prevent resource exhaustion from affecting other containers or the host:
# Limit CPU and memory
docker run \
--memory=512m \
--memory-swap=512m \
--cpus=1.0 \
myapp:latest
Memory limits prevent single containers from consuming all host memory. CPU limits prevent compute-intensive containers from affecting other workloads.
Docker Compose Security
Compose configurations should include security settings for production deployments:
version: '3.8'
services:
app:
image: myapp:latest
# Security settings
user: "1000:1000"
read_only: true
tmpfs:
- /tmp
# Resource limits
deploy:
resources:
limits:
cpus: '1.0'
memory: 512M
reservations:
cpus: '0.5'
memory: 256M
# Health check
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
# Logging with limits
logging:
driver: "json-file"
options:
max-size: "100m"
max-file: "3"
# Networks isolate services
networks:
- internal
# Secrets for sensitive data
secrets:
- db_password
- api_key
networks:
internal:
driver: bridge
internal: true
secrets:
db_password:
file: ./secrets/db_password.txt
api_key:
file: ./secrets/api_key.txt
Secrets Management
Never store secrets in environment variables or image layers. Use dedicated secrets management solutions.
Docker Secrets
Docker Swarm includes built-in secrets management. For single-node or development use, files provide a simple option:
# Create secret from file
echo "my-secret-password" | docker secret create db_password -
# Use secret in Compose
services:
db:
image: postgres:15-alpine
secrets:
- db_password
environment:
POSTGRES_PASSWORD_FILE: /run/secrets/db_password
External Secrets Management
For production deployments, dedicated secrets management tools provide additional features:
# Using HashiCorp Vault with external-secrets
apiVersion: v1
kind: Secret
metadata:
name: database-credentials
type: Opaque
stringData:
# References Vault secret
username:.secretRef: database-username
password:secretRef: database-password
Tools like HashiCorp Vault, AWS Secrets Manager, and Azure Key Vault integrate with Kubernetes and Docker to provide dynamic secret rotation and audit logging.
Network Security
Container networking requires attention to isolate traffic and prevent unauthorized access.
Network Segmentation
Create separate networks for different application tiers:
# Create custom networks
docker network create --driver bridge frontend
docker network create --driver bridge backend
# Run containers on appropriate networks
docker run \
--network frontend \
--network-alias web \
nginx:latest
docker run \
--network backend \
--network-alias database \
postgres:latest
Containers on the frontend network cannot directly access containers on the backend network without explicit connection. Your application code manages the connection between tiers.
Firewall Integration
Integrate Docker with host firewalls to control external access:
# Allow only specific ports
docker run -p 127.0.0.1:8080:80 nginx:latest
# The 127.0.0.1 binding ensures the port is only accessible locally
Bind to specific IP addresses to restrict which network interfaces accept connections. This prevents accidentally exposing services that should remain internal.
Monitoring and Auditing
Security requires ongoing monitoring to detect and respond to threats.
Container Monitoring
Monitor container behavior for anomalies:
# View container resource usage
docker stats
# Enable content trust for image verification
export DOCKER_CONTENT_TRUST=1
Docker Content Trust ensures you pull only signed images from trusted registries. This prevents supply chain attacks that inject malicious images into public registries.
Logging
Configure comprehensive logging to support incident investigation:
# View container logs
docker logs -f mycontainer
# Configure log drivers for central collection
docker run \
--log-driver=syslog \
--log-opt syslog-address=tcp://logserver:514 \
myapp:latest
Forward logs to a central logging system for analysis and retention. Ensure logs include sufficient detail for forensic investigation while respecting privacy requirements.
Image Signing and Verification
Image signing verifies that images originate from trusted sources and have not been tampered with.
Docker Content Trust
Enable content trust to require image signing:
# Enable content trust globally
export DOCKER_CONTENT_TRUST=1
# Or enable for specific repositories
export DOCKER_CONTENT_TRUST_SERVER=https://notary.example.com
export DOCKER_CONTENT_TRUST_CERT_PATH=/path/to/ca.crt
When enabled, Docker only pulls signed images. Publishers must sign images before distribution, preventing unsigned images from reaching your infrastructure.
Notary
Deploy a Notary server for private registries:
# Docker Compose for Notary
services:
notary-server:
image: notary-server:latest
ports:
- "4443:4443"
environment:
NOTARY_SERVER_STORAGE_TYPE: mysql
NOTARY_SERVER_TRUST_POOL: ""
Notary provides the infrastructure for signing and verifying images in private registries. Integrate it with your CI/CD pipeline to sign images automatically after successful builds.
Conclusion
Docker security requires attention across multiple layers, from base image selection through runtime configuration to monitoring. The practices in this guide create defense in depth that protects your applications.
Effective container security balances protection with operational requirements. Start with the measures that provide the greatest security benefit with minimal friction. Add additional layers as your threat model requires. Regular security reviews ensure your configurations remain effective as your applications evolve.
Related Posts: