Stop Trusting Default Configurations: A DevOps Survival Guide to Container Security
Letβs be honest for a second. The phrase "containers are secure by default" is the biggest lie in our industry. I learned this the hard way two years ago while debugging a cluster in Oslo. We had a developer accidentally expose a Docker socket in a CI/CD pipeline. Within 45 minutes, a botnet had scanned the port, spun up a privileged container, mounted the host filesystem, and injected a Monero miner into `/usr/bin`. The server didn't just crash; it melted due to CPU starvation, taking down three production databases with it.
That incident wasn't bad luck. It was negligence. In 2022, with supply chain attacks like Log4j still haunting our logs, running raw, unhardened containers is professional malpractice. Whether you are running Kubernetes (k8s) or simple Docker Compose stacks, isolation is your only defense.
This isn't high-level theory. This is how we lock down the stack, from the kernel up.
1. The Root Problem (Literally)
By default, a process inside a container runs as root. If an attacker compromises that process and breaks out of the container (via a kernel vulnerability like Dirty Pipe, CVE-2022-0847), they represent root on the host node. Game over.
You must enforce non-root execution. If your application "needs" root, you architected it wrong.
The Fix: Enforce User IDs
In your Dockerfile, create a specific user and switch to it. Never let the build finish as UID 0.
# The Wrong Way
FROM node:16-alpine
WORKDIR /app
COPY . .
CMD ["node", "index.js"]
# The Right Way
FROM node:16-alpine
WORKDIR /app
COPY . .
# Create a group and user
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
# Change ownership
RUN chown -R appuser:appgroup /app
# Switch context
USER appuser
CMD ["node", "index.js"]
In Kubernetes, you enforce this at the Pod level using `securityContext`. If a pod tries to run as root, the kubelet should reject it immediately.
apiVersion: v1
kind: Pod
metadata:
name: secured-pod
spec:
securityContext:
runAsNonRoot: true
runAsUser: 1000
runAsGroup: 3000
fsGroup: 2000
containers:
- name: app
image: my-app:1.2.0
2. Dropping Capabilities: The Principle of Least Privilege
Even a non-root user can sometimes exploit kernel capabilities. The Linux kernel divides privileges into distinct units called capabilities (e.g., `CAP_NET_ADMIN`, `CAP_CHOWN`). Docker gives you a default set that is usually too permissive for a web app.
Does your Nginx container need to change system time? No. Does your Python worker need to modify network interfaces? Absolutely not. Drop everything, then add back only what is strictly necessary.
Pro Tip: Start with `ALL` dropped. It breaks things initially, but it forces you to understand exactly what syscalls your application makes.
# Docker CLI example
docker run --cap-drop=ALL --cap-add=NET_BIND_SERVICE my-web-server
In Kubernetes 1.24 (which just dropped dockershim, remember?), we configure this in the container spec:
containers:
- name: web
image: nginx:1.21
securityContext:
capabilities:
drop:
- ALL
add:
- NET_BIND_SERVICE
3. The Immutable Infrastructure: Read-Only Filesystems
If an attacker gets a shell in your container, their first move is to download a payload (wget/curl) and execute it. Make their life miserable by making the root filesystem read-only. They can't write exploit scripts to disk if the disk is immutable.
For temporary files (logs, cache), mount a specific `emptyDir` volume.
securityContext:
readOnlyRootFilesystem: true
volumeMounts:
- mountPath: /tmp
name: tmp-volume
4. Infrastructure Matters: The CoolVDS Advantage
Here is the uncomfortable truth about shared hosting: "Container-based VPS" (often OpenVZ or LXC) usually shares the host kernel directly with your neighbors. If a neighbor triggers a kernel panic, your server goes down. If they exploit a kernel bug, they could theoretically access your memory.
This is why at CoolVDS, we strictly use KVM (Kernel-based Virtual Machine). Every VDS instance has its own dedicated kernel. Your containers run inside a VM that is hardware-isolated from other tenants. Even if you mess up your container security configuration, the blast radius is contained to your own VDS instance, not the entire physical node.
When you are serving data to Norwegian customers, latency and compliance are critical. Our Oslo datacenter ensures your data stays within Norwegian borders (satisfying Datatilsynet requirements regarding Schrems II), but more importantly, the KVM architecture ensures that your compute resources are yours alone. No noisy neighbors stealing CPU cycles during peak traffic.
5. Network Policies: Zero Trust Inside the Cluster
By default, all pods in a Kubernetes cluster can talk to each other. Your frontend can talk to your database. But your logging agent can also talk to your database. And your compromised dev sandbox can talk to your production database.
Implement a "Default Deny" policy immediately. Whitelist traffic explicitly.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
namespace: production
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
This policy cuts off all traffic. You then create specific policies to allow traffic, for example, allowing the frontend to reach the backend on port 8080.
6. Scanning the Supply Chain
You can't secure what you don't know. In 2022, scanning images for CVEs (Common Vulnerabilities and Exposures) is mandatory. We use Trivy because it's fast, integrates with CI pipelines, and catches OS-level packages and language-specific dependencies.
# Install Trivy (v0.29.2 is current stable)
sudo apt-get install wget apt-transport-https gnupg lsb-release
wget -qO - https://aquasecurity.github.io/trivy-repo/deb/public.key | sudo apt-key add -
echo deb https://aquasecurity.github.io/trivy-repo/deb $(lsb_release -sc) main | sudo tee -a /etc/apt/sources.list.d/trivy.list
sudo apt-get update
sudo apt-get install trivy
# Scan an image
trivy image python:3.4-alpine
The output will likely terrify you. Fix the criticals. Ignore the lows if you must, but never deploy a "Critical" CVE to production.
Conclusion
Container security isn't a product you buy; it's a discipline you practice. It requires a layered approach: secure the code, harden the container runtime, lock down the network, and run it all on isolated infrastructure.
For the underlying hardware, don't gamble with shared kernels. You need the isolation of KVM and the speed of local NVMe storage.
Ready to harden your stack? Deploy a CoolVDS instance in Oslo today. With full root access and KVM isolation, itβs the solid foundation your containers deserve. Start your 14-day trial here.