Every container on a Linux host shares one kernel, and that single fact is the whole story of container security. Namespaces give a process a private view of PIDs, mounts, and networking; cgroups cap what it can consume; seccomp filters which syscalls it can even attempt. Docker's default seccomp profile is a blocklist-style filter that denies roughly 44 of the 300-plus syscalls available on Linux x86_64, blocking dangerous ones like ptrace, keyctl , mount , and reboot outright. That layered model held up as "good enough" for years, until February 2019, when researchers disclosed CVE-2019-5736: a flaw in runc , the low-level runtime underneath Docker and Kubernetes, that let any process with root inside a container overwrite the host's runc binary via /proc/self/exe and execute code on the host — with zero non-default configuration required. It was a reminder that namespaces isolate views, not kernels, and that a single shared kernel vulnerability defeats the whole stack. This post breaks down what each isolation layer actually does, where it stops, and what changes when you're running workloads from different tenants on the same host.
What do Linux namespaces actually isolate?
Namespaces give each container a private view of specific kernel resources: PID namespaces make a container's process tree start at PID 1 and hide host processes; network namespaces give it its own interfaces, routes, and ports; mount namespaces give it its own filesystem tree; and UTS, IPC, and user namespaces isolate hostnames, inter-process communication, and UID/GID mappings respectively. This is why a process inside a container can't see or signal processes on the host, and why two containers on the same host don't see each other's filesystems by default. But every one of these namespaces is a view into the same running kernel — there is no separate kernel instance per container the way there is with a virtual machine. If an attacker finds a bug in the kernel's namespace-handling code itself, or in any syscall the container can still reach, the "private view" stops mattering, because the underlying resource being virtualized was never actually duplicated. That's the structural reason namespaces are described as isolation for cooperative workloads, not a hard security boundary against an adversarial one.
What do cgroups protect against, and what do they not protect against?
Control groups (cgroups), in their v1 and now-default v2 form, bound and account for CPU shares, memory, block I/O, and process/thread counts per container, which is what stops one noisy or compromised container from starving every other tenant on the same host of resources. This is a real and necessary control on any multi-tenant box — without per-container memory and PID limits, a single fork bomb or memory leak in one customer's workload degrades every other customer's workload on that node. But cgroups answer an accounting question, not a confidentiality or integrity question. A cgroup limit does nothing to stop a container from reading another tenant's secrets if a kernel bug or misconfiguration lets it escape its mount or user namespace. Treating cgroup limits as a security boundary rather than a resource-fairness mechanism is a common and consequential misunderstanding on shared infrastructure.
How did CVE-2019-5736 show that namespaces alone aren't a security boundary?
CVE-2019-5736, disclosed in February 2019 and affecting runc versions before 1.0-rc7, let a malicious or compromised container overwrite the host runc binary on disk by exploiting how runc re-executes itself via /proc/self/exe when attaching to a running container. Any process with root inside the container — the default user for most container images at the time — could trigger this with no unusual host configuration, no privileged flag, and no volume mount misconfiguration required. The result was arbitrary code execution on the host itself, fully outside every namespace boundary the container was supposed to be confined by. The documented mitigations at the time were running SELinux in enforcing mode (which blocked the specific write), mounting the host's runc binary read-only, or upgrading to the patched runc release — three defenses that operate below and alongside namespaces, not through them. It remains one of the clearest public examples of why "the container is namespaced" was never sufficient as a security claim on its own.
What does seccomp add on top of namespaces?
Seccomp (secure computing mode) restricts which syscalls a process is allowed to make at all, filtering the interface between the container and the kernel rather than virtualizing kernel resources the way namespaces do. Docker's default seccomp profile blocks on the order of 44 syscalls out of the 300-plus available on Linux x86_64, explicitly blocking ones like mount , reboot , ptrace , and kexec_load that are almost never needed by application workloads but are exactly the kind of primitive an attacker uses to escalate out of a container, according to Docker's own security documentation. This matters because many real container-escape techniques, including variants of the runc class of bugs, depend on syscalls that a properly applied seccomp profile would have refused before the vulnerable code path was ever reached. The tradeoff is that overly permissive custom profiles — often created because a team hit a blocked syscall and disabled seccomp entirely rather than allow-listing the one syscall they needed — quietly remove this layer for a container that will keep running for years.
What does gVisor add, and what does it cost?
gVisor, released as an open-source project by Google in 2018, takes a fundamentally different approach: instead of the container's syscalls going to the host kernel directly, gVisor's user-space kernel (runsc) intercepts them and implements much of the Linux kernel's own logic itself, in Go, running with fewer privileges than the real kernel would need. Google describes this as an additional, independent layer between the application and the host kernel, so that a bug in gVisor's reimplementation of a syscall doesn't hand an attacker the same host-kernel access that a real kernel bug would. This is a materially stronger boundary than namespaces plus seccomp alone, because it doesn't rely on getting every allow-list rule right against every kernel CVE — it changes what the container is talking to in the first place. The tradeoff, documented by Google Cloud and the gVisor project itself, is performance: syscall-heavy and I/O-heavy workloads see measurable overhead from the interception layer, which is why gVisor and similar approaches (like Kata Containers' microVMs) tend to be reserved for the specific containers running genuinely untrusted or cross-tenant code, rather than applied host-wide.
What should a multi-tenant container host actually enforce?
A defensible baseline combines every layer above rather than substituting one for another: run containers as non-root with user-namespace remapping so a container-root compromise doesn't map to host-root; drop all Linux capabilities by default and add back only the specific ones a workload needs; keep Docker's default (or a stricter custom) seccomp profile enabled rather than disabling it to unblock one syscall; apply AppArmor or SELinux mandatory access control profiles in addition to seccomp; mount root filesystems read-only wherever the workload allows it; and enforce per-tenant cgroup limits on CPU, memory, and PIDs as a baseline, not a nice-to-have. For hosts that run workloads from different, mutually untrusting tenants on shared kernels, the additional step that namespaces and cgroups alone can't provide is a second isolation layer — gVisor or a microVM-based runtime like Kata Containers — around the specific containers where a kernel-level escape would cross a genuine trust boundary. None of this replaces keeping the runtime itself patched: CVE-2019-5736 was fixed in runc , not worked around, and every host still running an unpatched runtime is exposed regardless of how well its seccomp and capability policies are tuned. Continuous scanning that flags out-of-date container runtimes and base images is a useful complement to this hardening work — but it's a detection layer, not a substitute for actually configuring namespaces, cgroups, seccomp, and a second isolation boundary correctly in the first place.