Console Login

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

The "It Works on My Machine" Trap

Let’s be honest: most developers treat containers like lightweight virtual machines. They spin up a Docker container, map port 80, and call it a day. In the Norwegian dev circles I move in—from startup hubs in Oslo to enterprise basements in Stavanger—this negligence is terrifying. I recently audited a client's Kubernetes cluster where the Tiller service account had cluster-admin privileges. It was practically an open invitation for a cryptojacking script to turn their infrastructure into a mining rig.

We are seeing a massive shift in 2019. The Tesla cloud hack last year proved that even tech giants aren't immune to container misconfiguration. If you are deploying containers without hardening the runtime, the image, and the host, you are negligent. And with Datatilsynet (The Norwegian Data Protection Authority) watching GDPR compliance like a hawk, negligence is expensive.

1. The Root of All Evil: UID 0

By default, processes inside a Docker container run as root. If a vulnerability allows an attacker to break out of the container (a container breakout), they are root on your host. This is not theoretical; the runc vulnerability (CVE-2019-5736) discovered earlier this year showed us exactly how dangerous this is.

You must stop writing Dockerfiles that end with the default user. Create a specific user for your application.

FROM alpine:3.9

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

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

# Tell Docker to switch to this user
USER appuser

WORKDIR /home/appuser
COPY . .
CMD ["python3", "app.py"]

When you deploy this on a CoolVDS instance, even if the container is compromised, the attacker finds themselves trapped as a low-privilege user with nowhere to go.

2. Immutable Infrastructure: Read-Only Filesystems

If your application doesn't need to write to the disk, don't let it. A common attack vector is injecting malicious binaries or modifying configuration files at runtime. Docker allows you to mount the container's root filesystem as read-only.

When launching a container manually, use the flag:

docker run --read-only --tmpfs /run --tmpfs /tmp -d my-secure-app

In Kubernetes (v1.14), you define this in your securityContext:

apiVersion: v1
kind: Pod
metadata:
  name: secure-pod
spec:
  containers:
  - name: my-app
    image: my-secure-app:1.0
    securityContext:
      readOnlyRootFilesystem: true
    volumeMounts:
    - mountPath: /tmp
      name: tmp-volume
  volumes:
  - name: tmp-volume
    emptyDir: {}
Pro Tip: Many applications fail when the filesystem is read-only because they try to write logs or pid files. Map /tmp and /var/run as emptyDir volumes (in K8s) or tmpfs (in Docker) to solve this without compromising the root fs.

3. Drop Linux Capabilities

The Linux kernel divides the privileges of the root user into distinct units called capabilities. A web server needs to bind to a network port (NET_BIND_SERVICE), but it definitely doesn't need to modify kernel modules (SYS_MODULE) or change system time (SYS_TIME). Yet, Docker gives containers a wide array of capabilities by default.

The most secure approach is to drop all capabilities and add back only what is strictly necessary.

docker run --cap-drop=ALL --cap-add=NET_BIND_SERVICE nginx

This