Security is crucial when running containers in production. Container security requires a comprehensive approach that addresses vulnerabilities at all layers - from the host system and container runtime to application code and network communications. This guide covers essential security practices and configurations for Docker environments to help you build and maintain secure containerized applications.
Process isolation : Containers share the host kernel but run in isolated process spacesNetwork namespace separation : Each container has its own network stackResource limitations : Restricting CPU, memory, and other resources to prevent DoSFilesystem isolation : Container-specific filesystem with controlled access to hostUser namespace mapping : Mapping container users to non-privileged host usersControl groups (cgroups) : Kernel feature that limits and isolates resource usageNamespaces : Provide isolated workspace for each container (PID, NET, MNT, UTS, IPC)User permissions : Principle of least privilege for container processesGroup management : Proper use of Linux groups for access controlRole-based access control (RBAC) : Define who can perform actions on Docker resourcesAPI access restrictions : TLS authentication and authorization for Docker daemonRegistry authentication : Secure access to image registries with authenticationSecret management : Secure handling of credentials and sensitive informationContainer user restrictions : Running containers as non-root usersCPU limits : Prevent containers from consuming excessive CPUMemory constraints : Limit memory usage to prevent OOM conditionsStorage quotas : Control disk usage to prevent filesystem fillingNetwork rate limiting : Prevent bandwidth abuseProcess restrictions : Limit process creation and capabilitiesSyscall filtering : Restrict available system calls with seccompCapabilities : Fine-grained control over privileged operationsImage security is the foundation of container security - a vulnerable base image can compromise your entire application.
# Scan image for vulnerabilities using Snyk integration
docker scan my-image:latest
# Scan with Trivy (popular open-source scanner)
trivy image my-image:latest
# Use specific image versions instead of 'latest' tag
docker pull nginx:1.21.6-alpine
# Use minimal base images to reduce attack surface
docker pull alpine:3.15.0
# Sign and verify images with Docker Content Trust
export DOCKER_CONTENT_TRUST = 1
docker trust sign my-image:latest
docker trust inspect my-image:latest
# Verify image integrity with digest
docker pull nginx@sha256:2834dc507516af02784808c5f48b7cbe38b8ed5d0f4837f16e78d00deb7e7767
Best practices for secure images:
Use official or verified images when possibleKeep base images updated with security patchesBuild from minimal images like Alpine or distrolessPin specific versions with digests for immutabilityImplement vulnerability scanning in CI/CD pipelinesRemove unnecessary tools from production imagesSet up image signing for supply chain securityUse multi-stage builds to minimize final image sizeNever run containers as root Create a dedicated user in your Dockerfile: RUN useradd -r -u 1000 appuser
Switch to that user: USER appuser
Use the --user
flag: docker run --user 1000:1000 my-image
Use read-only root filesystem Prevent filesystem modifications: docker run --read-only my-image
Mount specific writable directories: docker run --read-only --tmpfs /tmp my-image
Specify writable volumes: docker run --read-only -v data:/data:rw my-image
Limit container capabilities Drop all capabilities and add only what's needed: docker run --cap-drop ALL --cap-add NET_BIND_SERVICE my-image
Common capabilities to consider: NET_BIND_SERVICE, CHOWN, DAC_OVERRIDE, SETUID, SETGID Set resource limits Prevent resource exhaustion: docker run --memory=512m --cpu-shares=512 my-image
Set CPU limits: docker run --cpus=0.5 my-image
(use 50% of one CPU) Configure ulimits: docker run --ulimit nofile=1024:1024 my-image
Use security options Apply seccomp profiles: docker run --security-opt seccomp=profile.json my-image
Enable AppArmor: docker run --security-opt apparmor=docker-default my-image
Disable privilege escalation: docker run --security-opt no-new-privileges my-image
Enable logging and monitoring Configure logging: docker run --log-driver=journald my-image
Set up container monitoring with tools like cAdvisor, Prometheus, or DataDog Implement audit logging for Docker daemon and API calls Use real-time security monitoring tools like Falco The following Docker Compose configuration demonstrates comprehensive security hardening:
version : '3.8'
services :
secure-app :
image : my-app:latest
# Prevent privilege escalation
security_opt :
- no-new-privileges:true
# Apply custom seccomp profile to restrict syscalls
- seccomp:security-profile.json
# Enable AppArmor profile (Linux)
- apparmor=docker-default
# Make root filesystem read-only
read_only : true
# Provide writable temporary storage
tmpfs :
- /tmp:size=64M,mode=1777
- /var/run:size=32M
- /var/cache:size=32M
# Run as non-root user
user : non-root-user
# Drop all capabilities by default
cap_drop :
- ALL
# Add only necessary capabilities
cap_add :
- NET_BIND_SERVICE # Allow binding to ports below 1024
# Set resource limits
deploy :
resources :
limits :
cpus : '0.50'
memory : 256M
reservations :
memory : 128M
# Health check for monitoring
healthcheck :
test : [ "CMD" , "curl" , "-f" , "http://localhost:8080/health" ]
interval : 30s
timeout : 10s
retries : 3
# Configure logging
logging :
driver : "json-file"
options :
max-size : "10m"
max-file : "3"
# Set environment variables
environment :
- NODE_ENV=production
# Network access control
networks :
- internal
# Proper restart policy
restart : unless-stopped
networks :
internal :
# Internal network with no direct external access
internal : true
Each of these security configurations serves a specific purpose:
no-new-privileges
prevents privilege escalation attacksSeccomp profiles restrict available system calls Read-only filesystem prevents malicious modifications Running as non-root minimizes potential damage from breaches Dropping capabilities implements least-privilege principle Resource limits prevent DoS attacks Health checks enable automatic detection of compromised containers Use custom networks to isolate container communication
# Create an isolated network
docker network create --driver bridge isolated_network
# Attach container to isolated network
docker run --network isolated_network my-app
Restrict external access with internal networks
# In docker-compose.yml
networks :
backend :
internal : true # No outbound connectivity
Enable TLS for encrypted communications
# Generate TLS certificates
openssl req -x509 -nodes -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365
Implement firewalls at host and container level
# Configure host firewall (ufw example)
ufw allow 80/tcp
ufw allow 443/tcp
ufw deny 8080/tcp
Monitor traffic with network monitoring tools
# Capture container traffic
docker run --net=container:target_container nicolaka/netshoot tcpdump -i eth0
Port binding restrictions - only expose necessary ports
# Bind only to specific interface
docker run -p 127.0.0.1:8080:80 nginx
Service-specific networks - separate frontend/backend
services :
frontend :
networks : [ frontend ]
backend :
networks : [ backend ]
database :
networks : [ backend ]
Network segmentation with multiple networks
docker network create frontend
docker network create backend
docker network create database
Traffic encryption with TLS and VPNs
# Docker Swarm overlay network with encryption
networks :
secure_overlay :
driver : overlay
driver_opts :
encrypted : "true"
Load balancer security - TLS termination and WAF
# Example with Traefik (secure headers)
labels:
- "traefik.http.middlewares.secure-headers.headers.sslRedirect=true"
- "traefik.http.middlewares.secure-headers.headers.stsSeconds=31536000"
Securing the Docker daemon is critical since it runs with root privileges and manages all containers.
# Generate CA and certificates for Docker daemon TLS
mkdir -p ~/.docker/tls
cd ~/.docker/tls
# Create a certificate authority
openssl genrsa -out ca-key.pem 4096
openssl req -new -x509 -days 365 -key ca-key.pem -out ca.pem
# Create server certificate
openssl genrsa -out server-key.pem 4096
openssl req -subj "/CN=docker-host" -new -key server-key.pem -out server.csr
openssl x509 -req -days 365 -in server.csr -CA ca.pem -CAkey ca-key.pem -CAcreateserial -out server-cert.pem
# Create client certificate
openssl genrsa -out key.pem 4096
openssl req -subj "/CN=client" -new -key key.pem -out client.csr
openssl x509 -req -days 365 -in client.csr -CA ca.pem -CAkey ca-key.pem -CAcreateserial -out cert.pem
# Configure TLS for Docker daemon
sudo dockerd \
--tlsverify \
--tlscacert=ca.pem \
--tlscert=server-cert.pem \
--tlskey=server-key.pem \
-H=0.0.0.0:2376 \
-H unix:///var/run/docker.sock
# Update Docker daemon configuration in daemon.json
cat << EOF | sudo tee /etc/docker/daemon.json
{
"tls": true,
"tlscacert": "/path/to/ca.pem",
"tlscert": "/path/to/server-cert.pem",
"tlskey": "/path/to/server-key.pem",
"hosts": ["tcp://0.0.0.0:2376", "unix:///var/run/docker.sock"],
"log-level": "info",
"icc": false,
"no-new-privileges": true,
"userland-proxy": false,
"live-restore": true,
"default-ulimits": {
"nofile": {
"Name": "nofile",
"Hard": 64000,
"Soft": 64000
}
}
}
EOF
# Restart Docker daemon
sudo systemctl restart docker
# Restrict Docker socket access
sudo chmod 660 /var/run/docker.sock
sudo chown root:docker /var/run/docker.sock
# Use Docker client with TLS
docker --tlsverify \
--tlscacert=ca.pem \
--tlscert=cert.pem \
--tlskey=key.pem \
-H=docker-host:2376 version
Key Docker daemon security configurations:
"icc": false
- Disable inter-container communication by default"no-new-privileges": true
- Prevent privilege escalation"userland-proxy": false
- Use iptables directly instead of userland proxy"live-restore": true
- Containers remain running if daemon is unavailable"default-ulimits"
- Set sensible default ulimits for all containersContent trust provides:
Image authenticity verification - Confirms images come from trusted sources
# Enable Docker Content Trust
export DOCKER_CONTENT_TRUST = 1
# Pull only signed images
docker pull nginx:1.21
Data integrity protection - Ensures images haven't been tampered with
# Verify image signature
docker trust inspect --pretty nginx:1.21
Publisher verification - Validates who published the image
# Add a trusted publisher
docker trust signer add --key cert.pem username/image
Supply chain security - Protects the entire container lifecycle
# Sign image after building
docker trust sign username/image:tag
Deployment policy enforcement - Only allow signed images to run
# In daemon.json
{
"content-trust" : {
"mode" : "enforced"
}
}
The implementation uses The Update Framework (TUF) and Notary for:
Root keys - Master trust keys (keep offline and secure)Target keys - Used for signing specific repositoriesSnapshot keys - Record the current state of the repositoryDelegation keys - Allow delegation of signing authorityTimestamp keys - Prevent replay attacksExample workflow:
# Generate and load signing keys
docker trust key generate my-signing-key
# Add yourself as a signer
docker trust signer add --key my-signing-key.pub myname myorg/myimage
# Sign image on push
docker trust sign myorg/myimage:latest
# Inspect signatures
docker trust inspect --pretty myorg/myimage:latest
Security tools help identify vulnerabilities, misconfigurations, and potential threats in your container ecosystem.
# Trivy - comprehensive vulnerability scanner
trivy image my-image:latest
# Detailed scan with Trivy
trivy image --severity HIGH,CRITICAL --format json --output results.json my-image:latest
# Clair scanner - static vulnerability analyzer
clair-scanner --ip $( hostname -i ) my-image:latest
# Docker Bench Security - checks for dozens of common best-practices
docker run -it --net host --pid host --userns host --cap-add audit_control \
-v /var/lib:/var/lib \
-v /var/run/docker.sock:/var/run/docker.sock \
-v /usr/lib/systemd:/usr/lib/systemd \
-v /etc:/etc --label docker_bench_security \
docker/docker-bench-security
# Anchore Engine - deep image inspection and policy evaluation
docker run -d --name anchore-engine anchore/anchore-engine
docker exec anchore-engine anchore-cli image add my-image:latest
docker exec anchore-engine anchore-cli image wait my-image:latest
docker exec anchore-engine anchore-cli image vuln my-image:latest os
# Falco - runtime security monitoring
docker run -d --name falco --privileged \
-v /var/run/docker.sock:/var/run/docker.sock \
-v /dev:/host/dev \
-v /proc:/host/proc:ro \
-v /boot:/host/boot:ro \
-v /lib/modules:/host/lib/modules:ro \
-v /usr:/host/usr:ro \
falcosecurity/falco:latest
# Syft - SBOM (Software Bill of Materials) generator
syft my-image:latest
# Grype - vulnerability scanner based on SBOM
grype my-image:latest
# Example GitHub Actions workflow with security scanning
name : Docker Security Scan
on :
push :
branches : [ main ]
pull_request :
branches : [ main ]
jobs :
security-scan :
runs-on : ubuntu-latest
steps :
- uses : actions/checkout@v3
- name : Build image
run : docker build -t test-image:${{ github.sha }} .
- name : Scan with Trivy
uses : aquasecurity/trivy-action@master
with :
image-ref : 'test-image:${{ github.sha }}'
format : 'sarif'
output : 'trivy-results.sarif'
severity : 'CRITICAL,HIGH'
- name : Upload Trivy scan results
uses : github/codeql-action/upload-sarif@v2
with :
sarif_file : 'trivy-results.sarif'
Encrypted at rest - Secrets are stored encrypted in the Swarm Raft logSecure transmission - Transmitted over TLS-encrypted networksVersion controlled - Track changes through secret versioningAccess restrictions - Only accessible to authorized servicesRuntime injection - Available as files in the container at runtimeCentralized management - Consistent across the Docker Swarm clusterNon-persistent - Not stored in container image or filesystem
# Create a secret from a file
docker secret create site_key ./site_key.pem
# Create a secret from standard input
echo "my-secure-password" | docker secret create db_password -
# List available secrets
docker secret ls
# Inspect a secret (metadata only)
docker secret inspect db_password
# Use secret in a service
docker service create \
--name secure-app \
--secret db_password \
--secret source=site_key,target=/etc/ssl/private/site.key,mode= 0400 \
my-app:latest
# Inside the container, secrets are available at:
# /run/secrets/<secret_name>
# /run/secrets/site.key
# Remove a secret
docker secret rm db_password
version : '3.8'
services :
db :
image : postgres:13
environment :
POSTGRES_PASSWORD_FILE : /run/secrets/db_password
secrets :
- db_password
web :
image : nginx:alpine
secrets :
- source : site_certificate
target : /etc/nginx/ssl/site.crt
mode : 0444
- source : site_key
target : /etc/nginx/ssl/site.key
mode : 0400
secrets :
db_password :
file : ./secrets/db_password.txt
site_certificate :
file : ./secrets/site.crt
site_key :
file : ./secrets/site.key
# Using external secrets from vault
version : '3.8'
services :
app :
image : myapp:latest
environment :
- DB_PASSWORD=${DB_PASSWORD} # Injected via environment
# With docker-compose exec -e DB_PASSWORD=$(vault read -field=password secret/db)
Key compliance areas:
Container runtime security Implement seccomp and AppArmor profiles Enforce non-root containers with proper user namespaces Use read-only filesystems where possible Apply CIS Docker Benchmarks (Center for Internet Security) Implement runtime monitoring with tools like Falco Image security policies Establish baseline security requirements for all images Enforce vulnerability scanning before deployment Require signed images with content trust Maintain approved image inventory Document base image update procedures Generate and maintain SBOMs (Software Bill of Materials) Network security controls Implement network segmentation with proper isolation Encrypt all sensitive traffic (TLS/mTLS) Use internal networks for service-to-service communication Set up proper ingress and egress filtering Implement network policies to restrict traffic flow Monitor for unusual network activity Access control policies Implement role-based access control (RBAC) Use the principle of least privilege Require MFA for administrative access Rotate credentials regularly Audit access logs Implement privilege management for Docker resources Audit logging Configure comprehensive logging for all components:
# Enable Docker daemon audit logging
dockerd --log-level=info --log-driver=journald
Centralize logs for analysis:
logging :
driver : "fluentd"
options :
fluentd-address : "localhost:24224"
Set appropriate log retention policies Implement log integrity protection Create alerts for security-relevant events Establish log review procedures Vulnerability management Regular scanning of images and running containers Automated patching processes Clear vulnerability response procedures Vulnerability tracking and remediation timelines Documented exception process Threat intelligence integration Regulatory compliance Map container controls to specific requirements (PCI DSS, HIPAA, SOC2, etc.) Document container security architecture Implement required separation of duties Conduct regular compliance assessments Maintain evidence of control effectiveness Comprehensive security monitoring enables detection of suspicious activities and potential security incidents.
# Configure Docker daemon logging
dockerd \
--log-driver json-file \
--log-opt max-size=10m \
--log-opt max-file= 3 \
--log-level info
# Configure daemon.json for logging
cat << EOF > /etc/docker/daemon.json
{
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "5",
"labels": "production_status",
"env": "os,customer"
},
"debug": true,
"experimental": false
}
EOF
# Monitor real-time container events
docker events --filter 'type=container' --format '{{.ID}} {{.Type}} {{.Action}}'
# Check container resource usage with detailed stats
docker stats --all --format "table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}\t{{.NetIO}}\t{{.BlockIO}}"
# Monitor container lifecycle events
docker events --filter 'type=container' --filter 'event=start' --filter 'event=die'
# Configure container logging
docker run --log-driver=syslog --log-opt syslog-address=udp://syslog-server:514 nginx
# Use cAdvisor for container monitoring
docker run \
--volume=/:/rootfs:ro \
--volume=/var/run:/var/run:ro \
--volume=/sys:/sys:ro \
--volume=/var/lib/docker/:/var/lib/docker:ro \
--publish=8080:8080 \
--detach=true \
--name=cadvisor \
--restart=always \
google/cadvisor:latest
# Use Prometheus for metrics collection
docker run -d \
--name=prometheus \
--publish=9090:9090 \
--volume=/path/to/prometheus.yml:/etc/prometheus/prometheus.yml \
prom/prometheus:latest
# Use Falco for runtime security monitoring
docker run -d \
--name falco \
--privileged \
-v /var/run/docker.sock:/var/run/docker.sock \
-v /dev:/host/dev \
-v /proc:/host/proc:ro \
-v /boot:/host/boot:ro \
-v /lib/modules:/host/lib/modules:ro \
-v /usr:/host/usr:ro \
falcosecurity/falco:latest
# prometheus.yml
global :
scrape_interval : 15s
scrape_configs :
- job_name : 'cadvisor'
static_configs :
- targets : [ 'cadvisor:8080' ]
- job_name : 'node-exporter'
static_configs :
- targets : [ 'node-exporter:9100' ]
# falco_rules.yaml
- rule : Container Shell Activity
desc : A shell was spawned inside a container
condition : container.id != "" and proc.name = bash
output : "Shell spawned in container (user=%user.name container=%container.name container_id=%container.id)"
priority : WARNING
- rule : Unauthorized Files Access
desc : Detect attempts to access sensitive files
condition : >
container and
(fd.name startswith "/etc/shadow" or
fd.name startswith "/etc/passwd")
output : "Sensitive file accessed (user=%user.name file=%fd.name container=%container.name)"
priority : ALERT
Monitor logs - Collect and analyze logs from all components
# Centralize Docker logs
docker run \
--name fluentd \
-p 24224:24224 \
-v /path/to/fluent.conf:/fluentd/etc/fluent.conf \
fluent/fluentd:latest
Track resource usage - Detect abnormal resource patterns
# Set up alerting for unusual resource usage
docker run -d \
-p 9090:9090 \
-v /path/to/prometheus.yml:/etc/prometheus/prometheus.yml \
-v /path/to/alerts.yml:/etc/prometheus/alerts.yml \
prom/prometheus
Alert on anomalies - Configure notification systems
version : '3.8'
services :
app :
security_opt :
- apparmor:custom-profile
# Run container with SELinux context
docker run --security-opt label=level:s0:c100,c200 my-app
{
"defaultAction" : "SCMP_ACT_ERRNO" ,
"architectures" : [ "SCMP_ARCH_X86_64" ],
"syscalls" : [
{
"names" : [ "accept" , "bind" , "listen" ],
"action" : "SCMP_ACT_ALLOW"
}
]
}
Essential security measures:
Regular security updates Minimal base images Proper access controls Network segmentation Resource limitations Security scanning Audit logging Secrets management Compliance monitoring Incident response plan