Overview
This guide covers essential security practices for Docker environments in production. Containers share the host kernel, making security boundaries different from traditional VMs and requiring specific hardening measures.
Who Should Use This Guide
- DevOps engineers deploying containerized applications
- Security teams auditing container infrastructure
- System administrators managing Docker hosts
What You Will Learn
- Securing container images from build to deployment
- Runtime protection and isolation techniques
- Network segmentation for containers
- Secrets management best practices
Requirements
System Requirements
| Component | Requirement |
|---|---|
| Docker Engine | 20.10 or later |
| Operating System | Linux (Ubuntu 22.04+, RHEL 8+) or Windows Server 2022 |
| Privileges | Root/sudo access on Docker host |
Prerequisites
- Docker installed and running
- Basic understanding of Dockerfile syntax
- Familiarity with docker-compose
Tools Referenced
| Tool | Purpose | Installation |
|---|---|---|
| Trivy | Image vulnerability scanning | apt install trivy or container |
| Docker Scout | Built-in vulnerability scanning | Included with Docker Desktop |
| Grype | Alternative image scanner | curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | sh |
Process
Step 1: Secure Base Images
Use official, verified base images and pin specific versions.
Configuration:
# Recommended: Official image with specific version
FROM python:3.12-slim-bookworm
# Avoid: Unverified sources or latest tags
# FROM random-user/python:latestVerification:
# Check image source and history
docker inspect <your-image> | grep -A 5 "RepoTags"
docker history <your-image>Expected Output: Image should trace back to official Docker Hub repositories.
Step 2: Scan Images for Vulnerabilities
Integrate scanning into your CI/CD pipeline before deployment.
Using Trivy:
trivy image <your-image>:<tag>Using Docker Scout:
docker scout cves <your-image>:<tag>Using Grype:
grype <your-image>:<tag>Expected Output: Vulnerability report with severity levels (CRITICAL, HIGH, MEDIUM, LOW).
Acceptance Criteria: No CRITICAL or HIGH vulnerabilities in production images.
Step 3: Implement Multi-Stage Builds
Reduce attack surface by excluding build tools from production images.
Configuration:
# Build stage
FROM golang:1.22 AS builder
WORKDIR /app
COPY . .
RUN CGO_ENABLED=0 go build -o main .
# Production stage - minimal image
FROM gcr.io/distroless/static-debian12
COPY --from=builder /app/main /
USER nonroot
ENTRYPOINT ["/main"]Verification:
# Compare image sizes
docker images | grep <your-image>Expected Result: Production image significantly smaller than build image.
Step 4: Run Containers as Non-Root
Never run containers as root user.
Dockerfile Configuration:
# Create non-root user
RUN groupadd -r appgroup && useradd -r -g appgroup appuser
# Set ownership
COPY --chown=appuser:appgroup . /app
# Switch to non-root user
USER appuserVerification:
# Check running user inside container
docker exec <container-id> whoamiExpected Output: appuser (or your defined non-root user)
Step 5: Drop Unnecessary Capabilities
Remove Linux capabilities that containers don't need.
docker-compose.yml Configuration:
services:
app:
image: <your-image>
cap_drop:
- ALL
cap_add:
- NET_BIND_SERVICE # Only if binding to ports below 1024Verification:
# Check container capabilities
docker inspect <container-id> | grep -A 20 "CapDrop"Step 6: Enable Read-Only File System
Prevent runtime modifications to container filesystem.
docker-compose.yml Configuration:
services:
app:
image: <your-image>
read_only: true
tmpfs:
- /tmp
- /var/runVerification:
# Attempt to write to filesystem (should fail)
docker exec <container-id> touch /testfileExpected Output: touch: cannot touch '/testfile': Read-only file system
Step 7: Set Resource Limits
Prevent resource exhaustion attacks.
docker-compose.yml Configuration:
services:
app:
image: <your-image>
deploy:
resources:
limits:
cpus: '0.5'
memory: 512M
reservations:
cpus: '0.25'
memory: 256MVerification:
docker stats <container-id>Step 8: Implement Network Segmentation
Isolate containers using custom networks.
docker-compose.yml Configuration:
services:
frontend:
networks:
- frontend-net
backend:
networks:
- frontend-net
- backend-net
database:
networks:
- backend-net # Not accessible from frontend
networks:
frontend-net:
backend-net:
internal: true # No external accessVerification:
# List networks
docker network ls
# Inspect network connectivity
docker network inspect <network-name>Step 9: Secure Secrets Management
Never hardcode secrets in images or compose files.
Incorrect (Never Do This):
# BAD - Never hardcode secrets
ENV DATABASE_PASSWORD=<password>Correct - Using Docker Secrets (Swarm Mode):
services:
app:
secrets:
- db_password
secrets:
db_password:
external: trueCorrect - Using Environment Files:
# Create .env file (never commit to git)
echo "DB_PASSWORD=<your-secure-password>" > .env
chmod 600 .envservices:
app:
env_file:
- .envStep 10: Configure Docker Daemon Security
Harden the Docker daemon configuration.
Create /etc/docker/daemon.json:
{
"icc": false,
"userns-remap": "default",
"no-new-privileges": true,
"live-restore": true,
"userland-proxy": false,
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3"
}
}Apply Configuration:
sudo systemctl restart dockerVerification:
docker info | grep -E "Security|Logging"Troubleshooting
| Symptom | Possible Cause | Solution |
|---|---|---|
Container won't start with read_only: true | Application writes to filesystem | Add tmpfs mounts for required write paths |
Permission denied errors | Non-root user lacks file permissions | Ensure COPY --chown sets correct ownership |
| Container exits immediately | Entrypoint requires root | Modify application to run as non-root |
| Network connectivity issues | ICC disabled or wrong network | Verify containers are on correct networks |
| Secrets not available | Secret not created or wrong path | Check docker secret ls and mount paths |
Verification Checklist
Before deploying to production, verify:
- Using official/verified base images with pinned versions
- Images scanned for vulnerabilities (no CRITICAL/HIGH)
- Running as non-root user
- Unnecessary capabilities dropped
- Resource limits configured
- Networks properly segmented
- Secrets not hardcoded in images or compose files
- Read-only filesystem where possible
- Content trust enabled
- Logging configured
Security Scanning Pipeline Example
Integrate security scanning into CI/CD:
# .github/workflows/security.yml
name: Container Security
on: [push, pull_request]
jobs:
scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build image
run: docker build -t app:${{ github.sha }} .
- name: Run Trivy scan
uses: aquasecurity/trivy-action@master
with:
image-ref: 'app:${{ github.sha }}'
format: 'sarif'
output: 'trivy-results.sarif'
severity: 'CRITICAL,HIGH'
- name: Upload results
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: 'trivy-results.sarif'References
Last Updated: January 2026