Console Login

Container Security is Broken by Default: Hardening K8s and Docker in 2024

Container Security is Broken by Default: Hardening K8s and Docker in 2024

I once watched a production cluster in a FinTech startup near Barcode, Oslo, nearly implode because a junior developer committed a kubeconfig with cluster-admin privileges to a public repo. But frankly, that's amateur hour. The real terror comes from the silent misconfigurations sitting in your deployment manifests right now. If you are running containers with default settings, you are not secure. You are just lucky.

Containers are effectively lie-wrapped processes. They tell the application it has its own machine, but it’s just a namespace trick. Underneath, that dirty little process is making syscalls to the same kernel as everything else. In the context of Norwegian data privacy laws (referencing the latest tightening from Datatilsynet), reliance on default isolation is negligence. Let's fix it.

1. The "Root" of All Evil

By default, a container process runs as root. If an attacker breaks out of the container (hello, CVE-2024-21626 from earlier this year), they are root on the host. Game over. You need to strip privileges aggressively.

First, never build images that run as UID 0. Change your Dockerfile immediately:

# Don't do this
# FROM node:20

# Do this
FROM node:20-alpine
WORKDIR /app
COPY . .
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
USER appuser
CMD ["node", "index.js"]

But building correctly isn't enough. You must enforce it at the runtime level. In Kubernetes, if you don't explicitly forbid root, someone will eventually deploy a container that requests it.

Pro Tip: In Norway, compliance audits often check for "Least Privilege" enforcement. Using a Pod Security Admission (PSA) policy is the standard way to prove this since PodSecurityPolicies were deprecated.

Defining a Hardened SecurityContext

Here is the baseline configuration I insist on for any payload running on our infrastructure. This drops all Linux capabilities—most web apps don't need NET_ADMIN or SYS_TIME.

apiVersion: v1
kind: Pod
metadata:
  name: secure-nginx
spec:
  securityContext:
    runAsNonRoot: true
    runAsUser: 1000
    runAsGroup: 3000
    fsGroup: 2000
  containers:
  - name: nginx
    image: nginx:1.25.4-alpine
    securityContext:
      allowPrivilegeEscalation: false
      readOnlyRootFilesystem: true
      capabilities:
        drop:
          - ALL
    volumeMounts:
    - mountPath: /var/cache/nginx
      name: cache-volume
    - mountPath: /var/run
      name: run-volume
  volumes:
  - name: cache-volume
    emptyDir: {}
  - name: run-volume
    emptyDir: {}

Notice readOnlyRootFilesystem: true. This prevents an attacker from writing a malicious binary or script to the container's disk and executing it. It breaks many script-kiddie exploits instantly.

2. Supply Chain: Trust Nothing

You are likely pulling images from Docker Hub. Do you know what's inside them? A base image might contain a vulnerability disclosed yesterday. We use Trivy in our CI/CD pipelines before anything touches the staging environment.

Run this locally before you push:

trivy image --severity HIGH,CRITICAL python:3.9-slim

If you see vulnerabilities in system packages you don't use, switch to distroless images. They contain no shell, no package manager, and no noise.

3. Network Segmentation (The NIX Factor)

Norway's internet infrastructure is robust, and latency to the Norwegian Internet Exchange (NIX) is low, but that speed works both ways. If your internal cluster network is flat, a compromised frontend container can probe your database directly.

By default, all pods can talk to all pods. Block it. Use a default-deny NetworkPolicy.

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

Then, allow only necessary traffic. For example, allow the frontend to talk to the backend on port 8080 only:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-frontend-to-backend
  namespace: default
spec:
  podSelector:
    matchLabels:
      app: backend
  policyTypes:
  - Ingress
  ingress:
  - from:
    - podSelector:
        matchLabels:
          app: frontend
    ports:
    - protocol: TCP
      port: 8080

4. The Host Layer: Why Infrastructure Matters

This is where the rubber meets the road. All the container hardening in the world won't save you if the host kernel is vulnerable or if you are on a noisy, oversold shared hosting platform where a neighbor's DDoS impacts your CPU scheduler.

Containers share the kernel. If a kernel panic occurs, the whole node goes down. If a kernel exploit is found, isolation is breached.

This is why we architected CoolVDS differently. We don't mess around with "container-native" bare metal nonsense for multi-tenant environments. We use strict KVM virtualization. Your nodes get their own kernel. This adds a critical layer of defense-in-depth.

Feature Standard VPS / Cloud CoolVDS Implementation
Virtualization Often OpenVZ or Shared Kernel KVM (Hardware-assisted isolation)
Storage I/O SATA SSD (Throttled) NVMe (Direct access)
Data Location Vague "Europe" Norway / Northern Europe (Strict GDPR)

When running Kubernetes or Docker Swarm, the underlying I/O performance is critical for etcd stability. Slow I/O causes leader election failures. CoolVDS NVMe instances provide the low latency required for high-frequency writes in etcd, preventing the "split-brain" scenarios that keep DevOps engineers awake at 3 AM.

5. Runtime Defense with Falco

Static analysis is great, but what happens when an attack starts after deployment? You need runtime threat detection. Falco is the industry standard (CNCF graduated). It watches syscalls.

Here is a rule to detect if someone tries to spawn a shell inside a container (often the first step of a manual breach):

falco -r /etc/falco/falco_rules.yaml -M 45

And the rule definition:

- rule: Terminal shell in container
  desc: A shell was used as the entrypoint for the container user
  condition: >
    spawned_process and container
    and shell_procs and proc.tty != 0
    and container_entrypoint
  output: "Shell executed in container (user=%user.name %container.info)"
  priority: WARNING

Final Thoughts

Security is not a product you buy; it's a state of mind that assumes everything is already compromised. In 2024, with automated botnets scanning specifically for Kubernetes API ports (6443), you cannot afford laziness.

Don't put your hardened containers on a weak foundation. For critical workloads in Norway requiring strict data residency and uncompromising performance, you need a host that respects the physics of isolation.

Stop fighting with noisy neighbors. Deploy your secure cluster on CoolVDS today and see what dedicated NVMe performance actually feels like.