How to Harden Docker Containers: Security Best Practices

Docker container security hardening closes those gaps without sacrificing the convenience that made you choose containers in the first place.

docker

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.

Related Posts

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *