Console Login

Stop Running Containers as Root: A Battle-Hardened Security Guide for 2024

Stop Running Containers as Root: A Battle-Hardened Security Guide for 2024

I recently audited a Kubernetes cluster for a fintech startup in Oslo. They were proud of their CI/CD pipelines and sub-millisecond latency to the NIX (Norwegian Internet Exchange). Yet, a simple kubectl get pods -o yaml revealed the ugly truth: every single microservice was running as root. In the world of containerization, convenience is the enemy of security. If an attacker compromises a process running as root inside a container, and that container sits on a shared kernel with weak isolation, they are essentially root on the host node.

It’s 2024. We aren't just worried about script kiddies anymore; we are worried about automated supply chain attacks and sophisticated crypto-jacking bots scanning for exposed Docker sockets. If you are deploying containers in production without these controls, you are negligent. Here is how we lock it down, focusing on the specific constraints of the Nordic market where privacy (Datatilsynet) and reliability are non-negotiable.

1. The "Root" of All Evil: Enforcing Non-Root Users

By default, a process inside a Docker container runs as PID 1 with UID 0 (root). This is terrifying. If a vulnerability like runc (CVE-2019-5736 style) exploits the runtime, the attacker gains host root privileges. The fix is boring but essential: create a user.

In your Dockerfile, stop doing this:

FROM node:20-alpine
WORKDIR /app
COPY . .
CMD ["node", "index.js"]

And start doing this:

FROM node:20-alpine

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

WORKDIR /app
COPY . .

# Change ownership
RUN chown -R appuser:appgroup /app

# Switch to non-root user
USER appuser

CMD ["node", "index.js"]

For Kubernetes, you enforce this at the Pod level using SecurityContext. If you don't define this, Kubernetes defaults to what the image specifies (usually root). Explicitly deny it.

apiVersion: v1
kind: Pod
metadata:
  name: secured-pod
spec:
  securityContext:
    runAsNonRoot: true
    runAsUser: 1000
    runAsGroup: 3000
    fsGroup: 2000
  containers:
  - name: my-app
    image: my-secure-image:1.4.2
    securityContext:
      allowPrivilegeEscalation: false
      capabilities:
        drop:
          - ALL
Pro Tip: Dropping ALL capabilities is aggressive but necessary. Most web apps do not need NET_ADMIN or SYS_TIME. Add back only what you strictly need (e.g., NET_BIND_SERVICE if binding to port 80, though you should really bind to >1024).

2. Immutable Infrastructure: Read-Only Filesystems

Persistence is a lie we tell ourselves to feel safe. In a containerized environment, if a container is compromised, the attacker usually wants to download a payload (wget/curl) and execute it. Make their life miserable by making the root filesystem read-only.

This prevents attackers from writing malicious binaries or modifying configuration files at runtime. It also forces your developers to be disciplined about where they write data (tmpfs or mounted volumes only).

Docker CLI:

docker run --read-only --tmpfs /tmp:rw,noexec,nosuid my-app

Kubernetes Configuration:

securityContext:
  readOnlyRootFilesystem: true

This breaks many legacy applications that try to write logs to /var/log inside the container. The solution? Stream logs to stdout/stderr (the 12-factor app way) or mount an emptyDir volume at the logging path. Do not compromise security for bad code.

3. The Hardware Reality: Isolation Matters

Container security is software security. But software runs on hardware. If you are running containers on a "Shared Core" VPS from a budget provider, you are suffering from the "Noisy Neighbor" effect. Worse, if their hypervisor is unpatched, a kernel panic in a neighbor's container could impact your uptime.

This is where infrastructure choice becomes a security decision. At CoolVDS, we don't oversell cores. We use KVM (Kernel-based Virtual Machine) virtualization. This provides a hardware-assisted boundary between your Virtual Private Server and others.

Feature Standard Container Hosting CoolVDS (KVM)
Isolation Level OS Level (Shared Kernel) Hardware Level (Dedicated Kernel)
Neighbor Impact High (CPU Steal / Security Risk) Zero (Strict Resource Limits)
Custom Kernels Impossible Allowed (Load grsecurity/SELinux modules)

If you are handling sensitive Norwegian user data under GDPR, you need to prove you have taken "appropriate technical measures." Running your Docker host on a dedicated KVM slice is a defensible measure; running it on a shared, oversold OpenVZ container is not.

4. Supply Chain: Don't Pull 'Latest'

I saw a pipeline break last week because a dev used FROM python:latest. The upstream image updated, deprecated a library, and production went down. From a security standpoint, latest is an unknown quantity. It is unverified. It changes without warning.

Pin your images by SHA256 digest, not just tags. Tags are mutable; digests are immutable.

# Instead of python:3.11
FROM python@sha256:4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5

Furthermore, scan everything before it touches your cluster. Tools like Trivy have become the standard by 2024 for a reason. They are fast, accurate, and integrate into CI pipelines easily.

trivy image --severity HIGH,CRITICAL --ignore-unfixed my-app:1.0.0

If this command returns a non-zero exit code, the build should fail. No exceptions. Security debt collects interest faster than a payday loan.

5. Network Policies: The Firewall Inside

By default, in Kubernetes, all pods can talk to all other pods. If your frontend is compromised, the attacker has a direct line to your database pod. Network Policies act as a firewall inside the cluster.

Here is a "Deny All" policy that you should apply to every namespace immediately. Then, whitelist only what is necessary.

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-all
  namespace: default
spec:
  podSelector: {}
  policyTypes:
  - Ingress
  - Egress

For Norwegian businesses, data sovereignty is critical. Ensuring that traffic doesn't accidentally route through non-compliant regions starts with strict egress rules. Combine this with CoolVDS's low-latency network in Oslo, and you have a setup that pleases both the Datatilsynet and your users.

Final Thoughts

Container security isn't about buying a magic box. It's about layer-by-layer hardening. It's about stripping permissions, isolating processes, and ensuring the underlying infrastructure—the metal your code actually runs on—is solid.

Don't build a fortress on a swamp. Start with a solid foundation. Deploy your hardened containers on a CoolVDS NVMe instance today and experience the stability of true KVM isolation paired with Nordic connectivity.