Console Login

Container Breakouts are Real: Hardening Docker Environments in 2019

Container Breakouts are Real: Hardening Docker Environments in 2019

Let’s be honest with each other. For the last three years, developers have been treating Docker containers like lightweight Virtual Machines. They aren't. They are processes on a shared kernel, lying to themselves about isolation.

If you were sleeping under a rock in February, CVE-2019-5736 should have been your wake-up call. That vulnerability allowed a malicious container to overwrite the host's runc binary and gain root execution on the host server. Entire clusters were potentially compromised because we got lazy with our privileges.

I’ve cleaned up the mess after a crypto-jacking worm tore through a poorly configured Kubernetes cluster in Oslo last month. It wasn't pretty, and the latency spikes killed their legitimate traffic before we even found the miners. In this guide, we are going to lock down your container runtime using technologies available right now, in 2019. No fluff, just hardening.

1. The Root of All Evil

By default, processes inside a Docker container run as root. If a process breaks out of the namespace (which, as we've seen, happens), it is root on your host. This is negligent.

Stop writing Dockerfiles that end with a command running as ID 0. You need to create a dedicated user. It adds three lines to your build process and saves you a massive headache later.

FROM node:10-alpine

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

# Tell Docker to switch context
USER appuser

WORKDIR /home/appuser
COPY . .

CMD ["npm", "start"]

When you deploy this, even if the application contains a remote code execution vulnerability, the attacker lands in a shell with limited permissions. They can't install packages, they can't modify system binaries, and they certainly can't bind to privileged ports below 1024 without help.

2. Capabilities: Drop 'Em All

The Linux kernel divides root privileges into distinct units called capabilities. Docker gives you a broad set by default, including CHOWN, DAC_OVERRIDE, and FOWNER. Your Nginx web server does not need to change file ownership. It just needs to bind to a port and read files.

The philosophy here is simple: whitelist, don't blacklist. Drop everything, then add back only what is strictly necessary.

docker run --d -p 80:80 \
  --name web_server \
  --cap-drop=ALL \
  --cap-add=NET_BIND_SERVICE \
  --cap-add=SETUID \
  --cap-add=SETGID \
  nginx:1.15-alpine

If you are running a database like MySQL, you might need SYS_NICE for performance tuning, but for 90% of stateless microservices, NET_BIND_SERVICE is the only flag you need. This dramatically reduces the attack surface if the kernel is compromised.

3. The Storage Trap: Read-Only Filesystems

Immutability is a core tenant of DevOps, yet I see production servers where containers are writing logs to their own local filesystems. If an attacker can write to the filesystem, they can download payloads.

Force the container to run with a read-only root filesystem. Use tmpfs for the few paths that actually need to be writable (like /run or /tmp).

Pro Tip: Using --read-only forces you to be disciplined about where you store data. It separates your code from your state, which is crucial for scalability.
version: '3.7'
services:
  app:
    image: my-app:v1.2
    read_only: true
    tmpfs:
      - /tmp
      - /run
    volumes:
      - type: bind
        source: ./logs
        target: /var/log/app

4. KVM vs. Containers: The Isolation Reality

This is where infrastructure choice becomes a security decision. Many budget hosting providers in Europe use OS-level virtualization (like OpenVZ or LXC) to provision their VPS instances. In that scenario, your "server" is just a container on their huge host kernel. Your Docker containers are containers nested inside a container.

If a kernel panic occurs, or a noisy neighbor triggers an exploit, your stability evaporates. This is why CoolVDS strictly uses KVM (Kernel-based Virtual Machine) for all instances.

With KVM, you have your own dedicated kernel. It provides a hardware-level barrier between you and the other tenants on the physical hypervisor. For workloads involving PII (Personally Identifiable Information) under GDPR, relying on soft container isolation is a risk assessment nightmare. If you are serving customers in Norway, the latency to Oslo matters, but data integrity matters more.

5. AppArmor: The Last Line of Defense

AppArmor acts as a firewall for processes. It restricts what resources a specific program can access. Docker ships with a default profile, but for high-security environments, you should define your own.

Here is a snippet of a custom profile that denies writing to common web document roots, effective against defacement attacks:

#include 

profile docker-nginx flags=(attach_disconnected,mediate_deleted) {
  #include 

  network inet tcp,
  network inet udp,
  network inet icmp,

  # Deny writing to web root
  deny /usr/share/nginx/html/** w,

  # Allow reading configuration
  /etc/nginx/** r,
  /var/log/nginx/** w,
  /run/nginx.pid w,
  
  # Capabilities
  capability net_bind_service,
  capability setgid,
  capability setuid,
}

To apply this, load it into the kernel with apparmor_parser -r -W /path/to/profile and run your container with --security-opt apparmor=docker-nginx.

6. Auditing and Compliance

Under Norwegian law and GDPR, you must demonstrate that you have control over your data processors. Simply deploying Docker and hoping for the best is not a strategy. You need audit trails.

Install auditd on your host system and monitor the Docker socket. The Docker socket (/var/run/docker.sock) is effectively a root backdoor. If a container mounts this socket, it can kill other containers or wipe the host.

# Add this to /etc/audit/audit.rules
-w /usr/bin/docker -k docker
-w /var/lib/docker -k docker
-w /etc/docker -k docker
-w /var/run/docker.sock -k docker-socket

Conclusion

Container security in 2019 requires a shift in mindset. You cannot rely on defaults. The defaults optimize for ease of use, not security. By dropping capabilities, enforcing read-only filesystems, and ensuring your underlying infrastructure uses true hardware virtualization (like KVM on CoolVDS), you mitigate the risk of catastrophic breakouts.

Don't wait for the next runC exploit to hit the news. Secure your stack today.

Need a hardened environment for your next project? Deploy a CoolVDS NVMe instance in Oslo. We give you the raw KVM performance you need without the noisy neighbors.