The 5-Minute Docker Compose Security Checklist We Run for Every Client
3 security holes we find in every Docker Compose file. Exposed ports, root containers, no resource limits. 5-minute fix with copy-paste configs.
# The 5-Minute Docker Compose Security Checklist We Run for Every Client
We've reviewed Docker Compose configurations for over 30 startups. These three security holes appear in every single one. Without exception.
They're trivial to fix. Most teams just never do because nobody tells them until something goes wrong.
Hole #1: Ports Bound to 0.0.0.0
The most common Docker Compose pattern:
services:
postgres:
image: postgres:16
ports:
- "5432:5432" # ← This is 0.0.0.0:5432That "5432:5432" is shorthand for "0.0.0.0:5432:5432". Your database is now accessible from every network interface — including the public internet if your host has a public IP.
We've seen production Postgres instances exposed to the internet with default credentials. One client's Redis was mining crypto for 3 days before anyone noticed.
The Fix
services:
postgres:
image: postgres:16
ports:
- "127.0.0.1:5432:5432" # ← Only accessible from localhostFor services that only talk to each other via Docker network, remove the port binding entirely:
services:
postgres:
image: postgres:16
# No ports section at all — only reachable via Docker internal DNS
networks:
- backendRule: Only expose ports you need from outside Docker. If the service is internal-only, don't map it.
Hole #2: Running as Root
Check your running containers right now:
docker compose exec app whoami
# Output: rootIf an attacker achieves container escape (CVE-2024-21626 in runc, for example), they land on the host as root. Full control. Game over.
The Fix
services:
app:
image: myapp:latest
user: "1000:1000"
security_opt:
- no-new-privileges:true
read_only: true
tmpfs:
- /tmpWhat each line does:
user: "1000:1000" — runs as non-root UIDno-new-privileges — prevents privilege escalation via setuid binariesread_only: true — container filesystem is immutabletmpfs: /tmp — gives the app a writable temp directory without persistent write accessCommon objection: "My app needs to write files." Use volumes for specific writable paths. Don't give the entire filesystem write access.
Hole #3: No Resource Limits
Without limits, a single container with a memory leak eats the entire host:
# Container using 14GB on a 16GB host
docker stats --no-stream
CONTAINER CPU % MEM USAGE / LIMIT MEM %
app 340% 14.2GiB / 15.6GiB 91.03%When this happens, the OOM killer starts murdering other containers. Your database goes down. Your monitoring goes down. Everything cascades.
The Fix
services:
app:
image: myapp:latest
deploy:
resources:
limits:
memory: 512M
cpus: '1.0'
reservations:
memory: 256M
cpus: '0.25'Limits = hard ceiling. Container gets OOM-killed if it exceeds this. Reservations = guaranteed minimum. Docker won't schedule other work into this space.
Rule of thumb: Set memory limit at 2x your app's normal working set. If your Node.js app uses 200MB normally, set limit to 512M. Enough headroom for spikes, tight enough to prevent runaway.
The Complete Hardened Template
Here's our baseline docker-compose.yml security config that we apply to every project:
services:
app:
image: myapp:latest
user: "1000:1000"
read_only: true
security_opt:
- no-new-privileges:true
cap_drop:
- ALL
cap_add:
- NET_BIND_SERVICE # Only if binding port <1024
tmpfs:
- /tmp
deploy:
resources:
limits:
memory: 512M
cpus: '1.0'
networks:
- backend
# No port binding — reverse proxy handles external access
postgres:
image: postgres:16-alpine
user: "999:999" # postgres user UID
read_only: true
security_opt:
- no-new-privileges:true
cap_drop:
- ALL
volumes:
- pgdata:/var/lib/postgresql/data
tmpfs:
- /tmp
- /run/postgresql
deploy:
resources:
limits:
memory: 1G
cpus: '2.0'
networks:
- backend
# No ports exposed — app connects via Docker DNS
traefik:
image: traefik:v3
ports:
- "0.0.0.0:443:443" # Only HTTPS exposed publicly
- "127.0.0.1:8080:8080" # Dashboard localhost only
# ... rest of configBonus: Automated Scanning
Add this to your CI to catch these issues before deploy:
# Install docker-compose-linter
pip install docker-compose-linter
# Scan for security issues
docker-compose-lint --security docker-compose.ymlOr use Trivy for image scanning:
trivy config docker-compose.ymlHow We Can Help
We run free 15-minute Docker security reviews. Share your docker-compose.yml (redact credentials), and we'll tell you exactly what's exposed, what's at risk, and how to fix it.
No pitch. Just fixes.
Book a review: techsaas.cloud/contacttechsaas.cloud/contacthttps://techsaas.cloud/contact
Need help with security?
TechSaaS provides expert consulting and managed services for cloud infrastructure, DevOps, and AI/ML operations.