Why Containers Aren’t a Security Boundary
Summary
- A container is process isolation, not a security boundary. Every container on a node shares one kernel, and the kernel is the real boundary.
- Insight: Treat a container escape as a "when", not an "if". The design goal is to make an escaped process useless, not to assume it can never escape.
- Key Concepts: shared kernel, namespaces, cgroups, container escape, blast radius, sandboxed runtimes, hard multi-tenancy.
- When to Use: Any time you host untrusted code, multiple tenants, or high-value workloads on shared nodes.
- Limitations/Trade-offs: True isolation (VMs, sandboxes) costs CPU, memory, and startup latency. You trade density and speed for a stronger boundary.
- Related Topics: the layers of pod security,
securityContext, seccomp and AppArmor, Pod Security Standards.
Foundations
A virtual machine gets its own kernel. The hypervisor sits between guests and hardware, so a compromised guest kernel is still trapped inside the VM.
A container gets no kernel of its own. It is a normal Linux process that the kernel presents a restricted view of the system to.
That restricted view is built from two kernel features. Namespaces limit what a process can see - its own PIDs, mounts, network, and users. cgroups limit what it can consume - CPU, memory, and IO.
Neither feature was designed as a security sandbox. They were built for isolation and resource accounting, and they lean on the kernel being correct.
So the boundary an attacker actually faces is the syscall interface of the shared host kernel. If a bug in that interface lets a process break out of its namespaces, it is now a process on the host.
This is why "the container is isolated" is a dangerous half-truth. It is isolated by policy, enforced by a kernel that every other tenant is also poking at.
Mechanics & Interactions
Consider the path from a compromised container to a compromised cluster.
An attacker gets code execution inside a pod, usually through an application vulnerability. Nothing about the container stops that first step.
If the container runs as root, they already have root inside the namespace. From there they probe the kernel for an escape, or they abuse a misconfiguration.
Misconfigurations are the common route, not exotic kernel exploits. A privileged container, a mounted host path, a shared host PID namespace, or a mounted Docker or containerd socket each hand over the host directly.
# Anti-pattern: this container effectively owns the node.
spec:
hostPID: true
containers:
- name: danger
image: alpine
securityContext:
privileged: true
volumeMounts:
- name: host
mountPath: /host
volumes:
- name: host
hostPath:
path: /privileged: true disables most kernel confinement. hostPID: true lets the process see and signal host processes. The hostPath mount gives it the host root filesystem.
Once on the host, the attacker reaches every other pod's memory and secrets, the kubelet, and the node's cloud credentials. The blast radius jumps from one pod to the whole node and often the cluster.
Each pod security layer narrows this path. Non-root plus dropped capabilities removes the easy escalation. seccomp shrinks the syscall attack surface the kernel exposes. NetworkPolicy stops lateral movement. RBAC limits what the stolen ServiceAccount token can do.
None of them makes the container a hard boundary. They make the escaped process weak enough that the escape does not pay off.
Advanced Considerations & Applications
When you genuinely need a boundary equal to a VM, use one.
Sandboxed runtimes give each pod a stronger barrier. gVisor intercepts syscalls in a user-space kernel, so the container never talks to the host kernel directly. Kata Containers run each pod in a lightweight VM with its own kernel.
You select them per workload with a RuntimeClass, so only the risky pods pay the cost.
apiVersion: node.k8s.io/v1
kind: RuntimeClass
metadata:
name: gvisor
handler: runsc
---
apiVersion: v1
kind: Pod
metadata:
name: untrusted
spec:
runtimeClassName: gvisor
containers:
- name: job
image: registry.example.com/tenant-job@sha256:abc123The cost is real. Sandboxes add syscall overhead and startup latency, and some workloads (heavy IO, certain device access) run noticeably slower.
Node-level separation is the other tool. Schedule untrusted or per-tenant workloads onto dedicated node pools with taints and tolerations, so a node compromise does not cross tenants.
For hard multi-tenancy - running code you do not trust from parties who do not trust each other - the honest answer is often separate clusters or sandboxed runtimes plus node isolation. Namespace isolation alone is not enough for a hostile tenant.
Remember the runtime on nodes is containerd via the CRI. Docker Engine is your build and dev tool; its daemon settings do not configure how pods are isolated in the cluster.
Common Misconceptions
"Containers are like lightweight VMs." They are lightweight, but they are not VMs. VMs virtualize hardware and carry their own kernel; containers share the host kernel.
"Rootless containers can't escape." Rootless and non-root dramatically reduce risk, but a kernel bug can still be exploited from an unprivileged process. It is one strong layer, not a guarantee.
"Namespaces are a security feature." They provide isolation, but they were not designed as a security sandbox and have had escape bugs. Depend on defense in depth, not on namespaces alone.
"My cloud provider isolates my pods." Managed control planes are isolated from you, but by default your own pods share your worker nodes and their kernel with each other.
"A container escape is unrealistic." Escapes via misconfiguration are routine, and kernel CVEs appear regularly. Plan for the escape and minimize what it yields.
FAQs
If containers are not a boundary, why use them? For packaging, density, and speed. Add the isolation you need on top with securityContext, seccomp, and, where required, a sandboxed runtime.
What is the single biggest risk? Privileged and root containers, plus host mounts and host namespaces. They convert an app compromise straight into a node compromise.
When do I actually need gVisor or Kata? When you run untrusted code, host multiple distrusting tenants, or have workloads where a node compromise is unacceptable.
Does a read-only root filesystem stop escapes? Not by itself, but it removes a common foothold by preventing the attacker from writing tools into the container.
Is this different on Windows or with microVMs? The principle holds: shared-kernel containers are not a hard boundary. MicroVM runtimes exist precisely to add one.
Related
- The Layers of Pod Security
- Pod Security Basics
- securityContext
- seccomp & AppArmor
- Pod Security Best Practices
Stack versions: This page was written for Kubernetes 1.36.2, Docker Engine 29.6.1 (BuildKit default), containerd (CRI runtime on nodes), Helm 3, Compose v2, Argo CD (latest - verify at build), and Gateway API (GA - verify controller support at build).