The Container "Sandbox" is a Lie
Let’s be honest with ourselves. If you are running Docker out of the box on a default Linux install, you aren't building a fortress. You are building a cardboard box with a "Keep Out" sign taped to it. I have spent the last week cleaning up a mess where a developer mounted /var/run/docker.sock into a CI container. The result? Root access to the host node in under four seconds. With the CJEU invalidating the Privacy Shield just last week (Schrems II), the legal spotlight is now burning as hot as the technical one. If you are hosting customer data in Norway or the EU, you can't afford a kernel panic or a container breakout.
Containers are processes, not VMs. They share the host kernel. That is their efficiency superpower and their security Achilles' heel. Here is how we lock them down before they go into production.
1. The "Root" of All Evil
By default, the process inside your container runs as root. If an attacker breaks out of the application code—say, via a remote code execution vulnerability in your Node.js app—they are root inside the container. If they then find a kernel vulnerability (and there is always a CVE waiting to be found), they are root on your host.
Stop doing this. Enforce a non-root user in your Dockerfile. It is the single most effective mitigation you can implement today.
# The Wrong Way
FROM node:14
COPY . /app
CMD ["node", "index.js"]
# The Right Way
FROM node:14-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 user
USER appuser
CMD ["node", "index.js"]
2. Drop Capabilities Like They Are Hot
Even if you run as a non-root user, the Linux kernel grants certain capabilities to the container runtime. Does your Nginx web server need to modify system time? Does it need to load kernel modules? Absolutely not.
Docker grants 14 capabilities by default. You probably need... three. We adopt a "deny all, permit some" approach. When we deploy containers on our internal management nodes at CoolVDS, we drop everything first.
docker run --d -p 80:80 \
--cap-drop=ALL \
--cap-add=NET_BIND_SERVICE \
--cap-add=SETUID \
--cap-add=SETGID \
--read-only \
nginx:mainline-alpine
Pro Tip: The--read-onlyflag mounts the container's root filesystem as read-only. If an attacker manages to download a malicious script, they can't write it to disk and execute it. You will need to mounttmpfsfor directories that actually need writing, like/var/runor/tmp.
3. Kubernetes SecurityContext: The YAML Shield
If you are orchestrating with Kubernetes (we are seeing more 1.18 clusters in Oslo lately), you need to define security contexts at the Pod level. Don't rely on defaults. This is especially critical if you are running multi-tenant workloads.
Here is a snippet from a standard deployment manifest we use for stateless microservices:
apiVersion: apps/v1
kind: Deployment
metadata:
name: secure-app
spec:
template:
spec:
securityContext:
runAsUser: 1000
runAsGroup: 3000
fsGroup: 2000
containers:
- name: my-app
image: my-app:1.2.0
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop:
- ALL
4. Network Policies: Don't Talk to Strangers
In a flat network, every pod can talk to every other pod. If your frontend gets compromised, it can start port scanning your database. Kubernetes NetworkPolicies are your firewall. We recommend a "Default Deny" policy for every namespace.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
namespace: default
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
You then whitelist specifically what is allowed. It is tedious, yes. But security is often about doing the tedious work so you don't have to explain a data breach to the Datatilsynet later.
5. The Infrastructure Reality: Dirty Neighbors
You can harden containers all day, but you are still sharing a kernel. In high-security environments, or simply when you want guaranteed performance without "noisy neighbor" syndrome (where another tenant steals your CPU cycles), the hypervisor is king.
This is where the architecture matters. At CoolVDS, we don't sell containers as VPS. We sell KVM (Kernel-based Virtual Machine) instances. KVM provides hardware-level virtualization. Your OS kernel is yours. Your memory pages are yours. If a neighbor crashes their kernel, your instance doesn't blink.
Comparison: Shared Kernel vs. KVM
| Feature | Container (LXC/Docker) | CoolVDS KVM Instance |
|---|---|---|
| Kernel Isolation | Shared (Weak) | Dedicated (Strong) |
| Boot Time | Milliseconds | Seconds |
| Disk I/O | Native | Near-Native (VirtIO NVMe) |
| Security | Process-level | Hardware-level |
6. Runtime Auditing with Falco
If you can't prevent it, detect it. Falco (a CNCF project) is excellent for this. It hooks into kernel syscalls to detect abnormal behavior. For example, if a binary inside a container spawns a shell, that is usually a bad sign.
# Detect shell in container
- rule: Terminal shell in container
desc: A shell was used as the entrypoint for a container
condition: >
spawned_process and container
and shell_procs and proc.tty != 0
and container_entrypoint
output: "Shell spawned in a container (user=%user.name %container.info)"
priority: WARNING
Conclusion: Layers of Defense
With GDPR tightening and Schrems II changing the landscape for data transfers to the US, hosting your infrastructure on local, compliant ground is step one. Step two is hardening that infrastructure. Don't trust defaults. Drop capabilities. Use read-only filesystems.
And when you need absolute isolation ensuring that your data stays strictly yours, put those containers inside a KVM-backed VPS. It is the only way to sleep soundly knowing your kernel is safe.
Ready to lock down your stack? Deploy a hardened KVM VPS on CoolVDS today. Our Oslo datacenter offers low-latency connectivity to NIX and pure NVMe storage for your encrypted databases.