Console Login

Container Security in 2019: Stop Handing Root to Hackers

Container Security in 2019: Stop Handing Root to Hackers

Let’s be honest. Most of you are treating Docker containers like lightweight Virtual Machines. You pull a generic image from Docker Hub, slap a CMD on it, and push it to production. It works, sure. The latency is low, the app scales.

But from a security perspective, you are essentially walking through downtown Oslo with your wallet taped to your forehead.

We saw the wake-up call in February with CVE-2019-5736. That runC vulnerability wasn't just a theoretical paper; it allowed a malicious container to overwrite the host runC binary and gain root execution on the host server. If you were running unpatched Docker on a shared kernel without proper isolation, you were toast.

I’ve spent the last month auditing Kubernetes clusters for a fintech client here in Norway. The findings? Terrifying. Here is how to lock down your containers before Datatilsynet comes knocking.

1. The Root Problem (Literally)

By default, a process inside a Docker container runs as PID 1 with root privileges. If an attacker compromises that process (via a Node.js dependency vulnerability or a PHP exploit), they are root inside the container. From there, breaking out to the host is significantly easier.

The Fix: Stop using root. Create a specific user in your Dockerfile. This is the single most effective change you can make today.

FROM alpine:3.9

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

# Tell Docker to use this user
USER appuser

ENTRYPOINT ["./my-secure-app"]

When you deploy this, verifying it is mandatory. Run the container and check whoami. If it says root, you failed.

2. Limit Kernel Capabilities

Even if you run as a non-root user, the Linux kernel exposes a lot of attack surface. Docker grants a broad set of capabilities by default (like CHOWN, NET_RAW, etc.). Your Nginx web server does not need to modify kernel modules or trace processes.

I follow a "deny all, permit some" strategy. Drop all capabilities and add back only what is strictly necessary. This frustrates attackers who rely on standard tools functioning normally.

docker run --cap-drop=ALL --cap-add=NET_BIND_SERVICE --name web-server nginx:alpine
Pro Tip: If you are using Docker Compose (version 3.7 is solid right now), add this to your YAML:
cap_drop: ["ALL"]
cap_add: ["NET_BIND_SERVICE"]

3. The Host Matter: Shared Kernel vs. KVM

This is where infrastructure choices become critical. Containers share the host OS kernel. If there is a kernel panic or a deep vulnerability, neighbors can be affected. In a pure container environment, isolation is software-defined.

This is why, for mission-critical workloads in Norway, I don't run containers on bare metal shared among different tenants. I run them inside a KVM (Kernel-based Virtual Machine) VPS.

At CoolVDS, we enforce strict KVM virtualization. This means your Docker host has its own dedicated kernel. Even if your container runtime explodes, the blast radius is contained to your VPS. You aren't exposing your data to the noisy neighbor next door. For GDPR compliance, this extra layer of isolation is often the difference between a minor incident and a reportable breach.

4. Immutable Infrastructure: Read-Only Filesystems

If an attacker gets a shell in your container, their first move is to download a payload or modify a configuration file. Stop them by making the root filesystem read-only.

This forces you to be disciplined about where you write data (volumes or tmpfs only), which is a good architectural practice anyway.

docker run --read-only --tmpfs /run --tmpfs /tmp -v /var/lib/mysql:/var/lib/mysql mysql:5.7

This simple flag --read-only breaks 90% of automated script-kiddie exploits because they can't write their backdoors to disk.

5. Supply Chain: Use Minimal Base Images

I still see developers using FROM ubuntu:18.04 for a simple Go binary. That image is over 60MB and contains hundreds of binaries (grep, awk, apt) that an attacker can use against you.

Switch to Alpine Linux or, if you want to be on the bleeding edge of 2019 best practices, Google’s distroless images. Distroless contains only your application and its runtime dependencies. No shell. No package manager.

Base Image Size Shell Available? Attack Surface
ubuntu:18.04 ~64MB Yes High
alpine:3.9 ~5MB Yes (ash) Low
gcr.io/distroless/static ~2MB No Minimal

6. Network Segregation and Local Latency

Don't publish ports globally unless you have to. Using -p 80:80 listens on all interfaces. If you have a private administrative interface, bind it to localhost or a private network interface only.

# Only listen on the local loopback for admin tools
docker run -p 127.0.0.1:8080:8080 admin-tool

Furthermore, consider where your host is. If your customers are in Oslo or Bergen, hosting in Frankfurt adds latency. Hosting in the US adds latency and legal headaches regarding the CLOUD Act. Deploying on CoolVDS instances in Norway ensures your data stays within Norwegian jurisdiction—a massive selling point when you are dealing with sensitive financial or health data under GDPR.

Final Thoughts

Container security isn't about buying expensive software. It's about reducing privileges and minimizing surface area. We are building the future of infrastructure, but we have to respect the dangers of the present.

If you need a sandbox to test these configurations without risking your production cluster, spin up a KVM-backed instance. It takes less than a minute, and you get the NVMe performance required to build images fast.

Secure your stack. Keep your data in Norway. Deploy on CoolVDS.