SecurityAugust 4, 20265 min read

How to Secure Docker Containers

Containers aren't secure by default. Run as non-root, drop capabilities, limit resources, scan images and lock down the daemon on your VPS.

NBy Nxeon

Docker makes deploying apps easy, but "runs in a container" doesn't mean "isolated and safe." A misconfigured container can read the host, run as root, or consume all your resources. This guide covers the practical hardening steps that matter most when running containers on a VPS.

Understand the trust model

Containers share the host kernel โ€” they're isolation, not a virtual machine. A container running as root that escapes is root on your host. So the goal is to reduce what each container *can* do, and shrink the blast radius if one is compromised. Start from a hardened host: SSH keys, a firewall, and automatic updates.

Don't run containers as root

By default, processes in a container run as root. Run as an unprivileged user instead. In your Dockerfile:

RUN useradd -r -u 1001 appuser
USER appuser

Or at runtime:

docker run --user 1001:1001 myimage

Drop Linux capabilities

Containers get a set of kernel capabilities by default, most of which apps never need. Drop them all and add back only what's required:

docker run --cap-drop ALL --cap-add NET_BIND_SERVICE myimage

Also prevent privilege escalation:

docker run --security-opt no-new-privileges myimage

Use read-only filesystems

If the app doesn't need to write to its filesystem, make it read-only and give it a small writable tmpfs where needed:

docker run --read-only --tmpfs /tmp myimage

{{SCREENSHOT}}

Limit resources

An unbounded container can starve the host. Cap memory and CPU so one container can't take down everything:

docker run --memory 512m --cpus 1.0 myimage

This also blunts a runaway or compromised container โ€” see tune Linux performance for host-level limits.

Never mount the Docker socket into a container

Bind-mounting /var/run/docker.sock into a container effectively grants root on the host โ€” anyone in that container can spawn privileged containers. Avoid it unless you fully understand the risk, and never on an internet-facing service.

Use minimal, pinned, scanned images

Smaller base images have less to exploit. Prefer -slim or alpine bases, and pin exact versions rather than latest:

FROM node:20.11-slim

Scan images for known vulnerabilities:

docker scout cves myimage
# or
trivy image myimage

Rebuild regularly so base-image security fixes land. Also scan the host itself โ€” see scan a Linux server for malware.

Keep secrets out of images

Never bake API keys into an image or Dockerfile โ€” they end up in layers anyone can extract. Pass them at runtime via environment or secret mounts, as covered in manage secrets and environment variables.

Apply it all declaratively with Compose

Typing long docker run flags is error-prone and easy to forget. A docker-compose.yml file captures every hardening option in version-controlled config, so your secure defaults are applied every time:

services:
  app:
    image: node:20.11-slim
    user: "1001:1001"
    read_only: true
    tmpfs:
      - /tmp
    cap_drop:
      - ALL
    security_opt:
      - no-new-privileges:true
    mem_limit: 512m
    cpus: 1.0
    ports:
      - "127.0.0.1:3000:3000"
    env_file:
      - /etc/myapp/env

Now the non-root user, read-only filesystem, dropped capabilities, resource limits, and localhost-only port binding are all declared in one reviewable file. This is the pattern to reach for as soon as you run more than a throwaway container โ€” it makes your security posture visible and repeatable, and it's trivial to audit in code review.

Keep the host and daemon patched

Container security ultimately rests on the host kernel, since that's what's shared. Keep the host patched with automatic security updates, and keep the Docker engine itself current โ€” daemon vulnerabilities are the highest-impact kind because they can affect every container at once. Restrict who can talk to the Docker daemon: membership of the docker group is effectively root, so treat it exactly like sudo access and grant it sparingly. On multi-tenant or high-risk hosts, consider rootless Docker or a userns remapping so the container's root maps to an unprivileged host user, adding one more boundary between a container escape and real root.

Firewall published ports

docker run -p can bypass your expectations by publishing to all interfaces. Bind to localhost when a port should only be reached by a reverse proxy:

docker run -p 127.0.0.1:3000:3000 myimage

FAQ

Are containers a security boundary?

They're a meaningful but imperfect one โ€” they share the host kernel. Treat them as isolation to reduce blast radius, not as a hard sandbox like a VM.

Why not run containers as root?

If a root container is compromised and escapes, the attacker is root on your host. Running as an unprivileged user with dropped capabilities dramatically limits the damage.

Is the Docker socket really that dangerous?

Yes. Access to the Docker socket is equivalent to root on the host. Never expose it to a container or over the network without extreme care.

How do I keep images secure over time?

Pin versions, rebuild regularly to pick up base-image patches, and scan with tools like Trivy or Docker Scout in your pipeline.

Container hardening checklist

Apply these defaults to every container and you've closed the common footguns:

  • Run as a non-root user (USER in the Dockerfile or --user).
  • Drop all capabilities, add back only what's needed, and set no-new-privileges.
  • Use a read-only filesystem with a small writable tmpfs where required.
  • Cap memory and CPU so one container can't starve the host.
  • Never mount the Docker socket into a container.
  • Use minimal, version-pinned base images and scan them for vulnerabilities.
  • Keep secrets out of images โ€” inject them at runtime.
  • Bind published ports to localhost when they should only be reached by a proxy.

Capture all of this in a docker-compose.yml so your secure defaults are declared, version-controlled, and applied every time rather than remembered flag by flag. And keep the host itself patched โ€” because containers share the kernel, the host's security ultimately underpins every container running on it. Get those two things right, declarative config and a hardened host, and containers give you fast deploys without the sharp edges.

Hardened containers on a hardened host give you fast deploys without the footguns. Run them with full root control on NVMe โ€” see Docker hosting and our security page.

#docker#containers#security#devops#vps#seobatch

Deploy your first server in under a minute

Creating an account is free and takes no card details. You pay when you deploy โ€” choose a billing term and pay from your wallet or by card at checkout.