Running containers in production without security hardening is like leaving your front door open. This checklist covers the essential practices for securing Docker containers from image build to runtime.
1. Run as Non-Root
By default, containers run as root. This is dangerous — a container escape as root means root on the host.
FROM node:20-alpine
# Create a non-root user
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
WORKDIR /app
COPY --chown=appuser:appgroup . .
RUN npm ci --only=production
USER appuser
CMD ["node", "server.js"]
Verify with docker exec <container> whoami — it should not return root.
2. Use Minimal Base Images
| Image | Size | Attack Surface |
|---|---|---|
ubuntu:22.04 | ~77MB | Large — includes many packages |
node:20 | ~340MB | Very large — full Debian |
node:20-alpine | ~50MB | Minimal — musl libc, few packages |
node:20-alpine + multi-stage | ~30MB | Smallest — only production deps |
gcr.io/distroless/nodejs20 | ~40MB | No shell, no package manager |
Fewer packages means fewer CVEs. Distroless images are ideal for production since they contain no shell, making exploitation significantly harder.
3. Read-Only Filesystem
Prevent malicious writes inside the container:
services:
api:
image: myorg/api:1.4.2
read_only: true
tmpfs:
- /tmp
- /var/run
Use tmpfs mounts for directories that need temporary write access.
4. Drop All Linux Capabilities
By default, Docker grants a set of Linux capabilities. Drop them all and add back only what you need:
services:
api:
image: myorg/api:1.4.2
cap_drop:
- ALL
cap_add:
- NET_BIND_SERVICE
Common capabilities and their use:
| Capability | Purpose |
|---|---|
NET_BIND_SERVICE | Bind to ports below 1024 |
CHOWN | Change file ownership |
SETUID / SETGID | Change process UID/GID |
SYS_PTRACE | Debug processes (avoid in prod) |
5. Seccomp Profiles
Seccomp filters restrict which system calls a container can make. Docker's default profile blocks ~44 dangerous syscalls. You can make it stricter:
{
"defaultAction": "SCMP_ACT_ERRNO",
"architectures": ["SCMP_ARCH_X86_64"],
"syscalls": [
{
"names": ["read", "write", "open", "close", "stat", "fstat", "mmap", "mprotect", "munmap", "brk", "exit_group"],
"action": "SCMP_ACT_ALLOW"
}
]
}
Apply it:
docker run --security-opt seccomp=custom-profile.json myimage
6. Image Scanning
Scan images for known vulnerabilities before deploying:
# Trivy (open-source, fast)
trivy image myorg/api:1.4.2
# Docker Scout (built into Docker Desktop)
docker scout cves myorg/api:1.4.2
# Grype (Anchore)
grype myorg/api:1.4.2
Integrate scanning into your CI pipeline so vulnerable images never reach production.
7. Secrets Management
Never bake secrets into images. Instead:
# Bad — secret visible in image layers
ENV DATABASE_PASSWORD=mysecret
# Good — mount as Docker secret
docker secret create db_password ./secret.txt
services:
api:
image: myorg/api:1.4.2
secrets:
- db_password
secrets:
db_password:
file: ./secrets/db_password.txt
Access the secret at /run/secrets/db_password inside the container.
8. Network Segmentation
Limit container-to-container communication:
networks:
frontend:
backend:
internal: true # No external access
services:
nginx:
networks: [frontend, backend]
api:
networks: [backend]
db:
networks: [backend]
The internal: true flag prevents containers on that network from reaching the internet.
9. Resource Limits
Prevent denial-of-service by limiting resources:
services:
api:
deploy:
resources:
limits:
cpus: "1.0"
memory: 512M
pids: 100
The pids limit prevents fork bombs.
10. Audit and Monitoring
- Enable Docker daemon audit logging
- Monitor container activity with Falco (runtime security)
- Set up alerts for privilege escalation attempts
- Regularly rotate and update images
Quick Reference Checklist
- Non-root user in Dockerfile
- Minimal / distroless base image
- Read-only filesystem where possible
- All capabilities dropped, only needed ones added
- Seccomp profile applied
- Images scanned in CI
- Secrets mounted, never baked
- Networks segmented
- Resource limits enforced
- Runtime monitoring enabled
Security is not a one-time task. Build these practices into your pipeline and review them regularly.
Enjoyed this article?
Get more DevOps insights delivered to your inbox.
Get new posts by email
Subscribe to get an email when a new blog post is published. Skip anytime.
No spam, unsubscribe anytime.
Related Posts
Discussion
0 comments
Sign in to join the conversation.
Be the first to comment
Start a conversation about this post