Console Login

Container Security in 2019: Stop Running Docker as Root or Get Hacked

Container Security in 2019: Stop Running Docker as Root or Get Hacked

If I see one more Dockerfile that ends without a USER instruction, I might actually disconnect the server rack myself. It is December 2019. We have known about the risks of running containers as root for years, yet I still audit setups in Oslo and Bergen where production databases are running with full privileges, just waiting for a CVE-2019-5736 exploit to break out and own the host.

Efficiency is the drug of the DevOps world, but security is the rehab. Containers are fantastic for density and deployment speed, but out of the box, Docker is not a security tool. It is a delivery mechanism.

In this guide, we are going to fix your docker run commands, strip dangerous capabilities, and discuss why your choice of VPS virtualization (KVM vs. OpenVZ) is the difference between a contained incident and a catastrophic data breach reported to Datatilsynet.

The "Root" of the Problem

Let’s be clear: Containers do not contain. Not by default.

Earlier this year, we saw the runc vulnerability (CVE-2019-5736). It allowed a malicious container to overwrite the host runc binary and gain root execution on the host machine. If you were running a shared kernel virtualization (like OpenVZ or LXC) on a budget provider, your neighbors could theoretically impact you. If you were running as root inside the container, you handed them the keys.

Pro Tip: Never assume the default configuration is secure. Docker optimizes for developer experience (DX), not security. It wants things to work immediately, which usually means giving the process way more permissions than it needs.

1. The Golden Rule: Drop Privileges

The easiest win is to stop using the root user. Create a specific user for your application. Here is how we configure our Nginx microservices at CoolVDS internal deployments:

FROM alpine:3.10

# Install dependencies
RUN apk add --no-cache nginx

# Create a non-root user and group
RUN addgroup -S appgroup && adduser -S appuser -G appgroup

# Fix permissions so the non-root user can write to logs/pid
RUN mkdir -p /run/nginx && \
    chown -R appuser:appgroup /run/nginx /var/lib/nginx /var/log/nginx

# Switch to non-root user
USER appuser

CMD ["nginx", "-g", "daemon off;"]

By switching to appuser, even if an attacker manages to exploit a buffer overflow in Nginx, they land in a shell with limited permissions. They cannot mount filesystems, they cannot stop services, and they certainly cannot install a crypto-miner easily.

2. Capability Dropping: The Surgeon's Scalpel

Linux capabilities break down the "root" superuser power into distinct units. By default, Docker grants a container a specific set of capabilities (like CHOWN, NET_BIND_SERVICE, etc.). Most web apps don't need half of them.

If you are just serving a Python Flask API or a Node.js app, why does it need AUDIT_WRITE or MKNOD? It doesn't. We adhere to a whitelist approach: drop everything, then add back only what is necessary.

docker run --d -p 8080:8080 \
  --name secure-app \
  --cap-drop=ALL \
  --cap-add=NET_BIND_SERVICE \
  --read-only \
  --tmpfs /tmp \
  my-secure-image:latest

Breakdown of these flags:

  • --cap-drop=ALL: Strips all kernel privileges. The container is neutered.
  • --cap-add=NET_BIND_SERVICE: Adds back the ability to bind to a port (if you are binding low ports, though typically we bind >1024).
  • --read-only: Mounts the container's root filesystem as read-only. Hackers can't download backdoors if they can't write to disk.
  • --tmpfs /tmp: Gives you a scratchpad in memory that vanishes on restart.

3. The Infrastructure Layer: KVM is Mandatory

This is where the "Nordic Reality" kicks in. We have strict data privacy laws. GDPR (Personvernforordningen) puts the burden on you to ensure data integrity.

If you run Docker on a cheap VPS provider using OpenVZ or LXC, you are sharing the kernel with every other customer on that physical node. If the kernel panics, everyone goes down. If a kernel exploit exists, isolation is weak.

At CoolVDS, we exclusively use KVM (Kernel-based Virtual Machine). Each VPS gets its own dedicated kernel. Docker runs on your kernel, which runs on our hypervisor. This adds a critical layer of defense-in-depth.

Feature Shared Kernel (OpenVZ/LXC) Dedicated Kernel (KVM / CoolVDS)
Kernel Isolation None (Shared with host) Complete (Hardware Virtualization)
Docker Compatibility Limited (Older versions, overlayfs issues) Native (Run latest Docker/K8s)
Security Risk High (Neighbor breakout possible) Low (Hypervisor barrier)
Swap Management Restricted Full Control

4. Network Segregation

Don't use the default bridge network for everything. If one container gets compromised, it can ARP spoof or sniff traffic from other containers on the default bridge.

Create dedicated networks for your application stacks. This isolates the traffic logically.

# Create a network for your database and app
docker network create --driver bridge --internal backend-net

# Attach only the DB to this internal network (no internet access)
docker run -d --name db --network backend-net postgres:12-alpine

# Attach app to both public and backend
docker run -d --name app --network backend-net -p 80:80 my-app

In this setup, the database has absolutely no route to the outside internet. Even if you misconfigure the firewall on the host, the Docker network driver prevents external routing to the DB container.

5. Filesystem Limits and Resources

Denial of Service (DoS) isn't always a network flood; sometimes it's a disk fill attack. A runaway log file in a container can fill up the host's NVMe storage, crashing the entire server.

We see this frequently with unmanaged logging. Docker's default logging driver is json-file, which creates log files that grow indefinitely. You need to rotate them.

Configuring Log Rotation in /etc/docker/daemon.json:

{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "10m",
    "max-file": "3"
  },
  "storage-driver": "overlay2"
}

Reload Docker after applying this: systemctl reload docker. This ensures no single container can consume more than 30MB of log space.

The Norwegian Context: Latency and Sovereignty

Security also means availability. If your data center is in Frankfurt or Amsterdam, you are adding 20-40ms of latency to your Norwegian users. For real-time applications, that lag is perceptible.

Hosting locally in Norway not only reduces latency to NIX (Norwegian Internet Exchange) to under 2ms but also simplifies your GDPR compliance stance. While the Privacy Shield framework (for US transfers) is technically active, the legal waters are murky. Keeping data on CoolVDS NVMe instances within Nordic jurisdiction removes the headache of explaining "Third Country" data transfers to your legal team.

Final Thoughts

Securing containers in 2019 isn't about buying expensive software. It is about configuration discipline. It is about understanding that root inside is root outside, and mitigating that risk through least-privilege principles and robust virtualization.

Don't build your castle on a swamp. Ensure your foundation is solid with KVM virtualization that guarantees resource isolation and kernel security.

Ready to harden your infrastructure? Deploy a secure KVM instance on CoolVDS today and get root (the safe kind) in less than 55 seconds.