With so many services throughout this series running in Docker — Nginx Proxy Manager, Gitea, Uptime Kuma, Grafana — it’s worth stepping back and addressing container security specifically, since default Docker configurations prioritize convenience over security in several meaningful ways. Docker container security hardening closes those gaps without sacrificing the convenience that made you choose containers in the first place.
Why Default Docker Isn’t Automatically Secure
Docker’s defaults favor getting containers running quickly and reliably, not necessarily hardened against a compromised container attempting to escalate its access. A container running as root, given more capabilities than it actually needs, or granted excessive access to the host, all represent common default patterns worth actively addressing.
Running Containers as Non-Root
By default, many container images run as root inside the container, which — combined with certain container escape vulnerabilities — carries significantly more risk than running as an unprivileged user. Where an image supports it, specify a non-root user explicitly:
yaml
services:
myapp:
image: someapp:latest
user: "1000:1000"
Many well-maintained images already default to a non-root user internally; check the image’s documentation to confirm rather than assuming.
Limiting Container Capabilities
Docker containers receive a default set of Linux capabilities, several of which most applications never actually need. Drop unnecessary capabilities and add back only what’s specifically required:
yaml
services:
myapp:
image: someapp:latest
cap_drop:
- ALL
cap_add:
- NET_BIND_SERVICE
This example drops every default capability, then adds back only the ability to bind to privileged ports — a much more restrictive baseline than Docker’s default capability set.
Avoiding Privileged Mode
The privileged: true flag grants a container nearly complete access to the host, including all devices and kernel capabilities — occasionally necessary for very specific use cases, but almost never actually required for typical self-hosted applications. Audit any compose files using this flag and confirm it’s genuinely necessary rather than a convenience shortcut copied from a guide.
Read-Only Root Filesystems
For containers that don’t need to write to their own filesystem beyond specifically mounted volumes, mark the root filesystem read-only:
yaml
services:
myapp:
image: someapp:latest
read_only: true
tmpfs:
- /tmp
This prevents a compromised container from modifying its own application files, while tmpfs provides a writable temporary directory for anything truly requiring one.
Limiting Resource Usage
Beyond security specifically, resource limits prevent a single misbehaving or compromised container from consuming resources needed by everything else on the host:
yaml
services:
myapp:
image: someapp:latest
deploy:
resources:
limits:
cpus: '1.0'
memory: 512M
Scanning Images for Known Vulnerabilities
Before deploying an image, scan it for known vulnerabilities using tools like Trivy:
trivy image someapp:latest
This checks the image against known CVE databases, surfacing vulnerable dependencies bundled into the image before you actually run it in production.
Keeping the Docker Socket Contained
Mounting the Docker socket (/var/run/docker.sock) into a container — necessary for tools like Watchtower () — grants that container effective root access to the entire host, since Docker socket access is equivalent to root. Only mount the socket into containers that really need it, and understand that doing so means fully trusting that specific container’s security.
Network Segmentation for Containers
Rather than running every container on Docker’s default bridge network where they can all potentially reach each other, use dedicated Docker networks per application stack, limiting which containers can actually communicate with which others — a container-level parallel to the VLAN segmentation :
yaml
networks:
frontend:
backend:
services:
webapp:
networks:
- frontend
- backend
database:
networks:
- backend
Regularly Updating Base Images
Combined with Watchtower () for automatic updates, periodically rebuild any custom images you maintain yourself against updated base images, ensuring inherited vulnerabilities from an outdated base don’t linger indefinitely in images you built yourself.
Final Thoughts
Applying Docker container security hardening practices — non-root users, minimal capabilities, avoiding privileged mode, and proper network segmentation — closes meaningful gaps left by Docker’s convenience-focused defaults. For a homelab running as many containerized services as this series covers, taking these steps for at least your most exposed or sensitive containers significantly reduces the impact potential of any single container compromise.

Leave a Reply