Skip to content
Blog

The Container Security Model: What Is and Is Not Isolated

Containers share one host kernel, so isolation is a bundle of kernel features, not a wall. NIST SP 800-190 frames the model — this post maps each layer to what it really isolates and how to harden it.

Published on • October 12, 2026

AI Assistant

“Containerized” gets used as a synonym for “isolated,” and that shortcut is expensive. A container is not a miniature VM with walls — it is a process (or group of processes) given a private view of the operating system by a bundle of kernel features. NIST SP 800-190, the Application Container Security Guide, is the canonical reference for this model, and its framing is worth internalizing: with application virtualization, the same shared OS kernel is exposed virtually to multiple discrete apps, and OS components keep each app instance isolated from the others. Every word of that matters — especially shared.

One kernel, many views

In a VM deployment, each instance sees virtualized hardware and carries a complete guest OS; the hypervisor is the security boundary. Containers work differently: multiple apps share the same OS kernel instance but are segregated from each other. NIST states the consequence without hedging: “While containers provide a strong degree of isolation, they do not offer as clear and concrete of a security boundary as a VM.” And in the risk analysis: “although containers provide strong software-level isolation of resources, the use of a shared kernel invariably results in a larger inter-object attack surface than seen with hypervisors.”

That is the core insight of the container security model. Isolation is not one mechanism you configure — it is several independent kernel features, each with defaults, each removable or widenable, all layered on top of the one thing every container has in common: the host kernel.

The isolation stack, layer by layer

Namespaces — what a process can see. Per NIST, namespace isolation limits which resources a container may interact with: “file systems, network interfaces, interprocess communications, host names, user information, and processes.” The guide’s own example is the classic one: run ps -A inside a container running Apache on a host with many other containers, and you only see httpd. Each container also gets its own networking stack with unique interfaces and IP addresses. Namespaces virtualize perception.

Control groups — what a process may use. cgroups implement resource accounting and limiting: the kernel meters memory, processor usage, and disk I/O per container. Docker’s engine security documentation is precise about the boundary here: cgroups “do not play a role in preventing one container from accessing or affecting the data and processes of another container,” but they are “essential to fend off some denial-of-service attacks” — one container cannot exhaust the host’s memory and take everything down with it.

The layered filesystem — copy-on-write isolation. Containers share the same physical storage without being able to access or alter each other’s files. Images are built in layers; when several containers run the same base, only one copy of a binary exists on disk, and changes are recorded separately, visible only to the container that made them. It is an efficiency feature and an isolation feature at once — but note what it isolates: the container’s view of the filesystem, not the kernel serving it.

Capabilities — root, minus the dangerous parts. Capabilities turn the binary root/non-root split into fine-grained grants. Docker starts containers with an allowlist: “By default Docker drops all capabilities except those needed,” which denies mount operations, raw sockets (packet spoofing), creating device nodes, and loading kernel modules. A process that just needs to bind port 80 gets net_bind_service instead of root. The payoff, in Docker’s words: “even if an intruder manages to escalate to root within a container, it is much harder to do serious damage, or to escalate to the host.”

Seccomp — what the kernel will even hear. Seccomp filters the system calls a process may make. Docker’s default profile “disables around 44 system calls out of 300+,” implemented as an allowlist with a default SCMP_ACT_ERRNO (permission denied) action. The blocked list reads like a container-escape museum: mount, pivot_root, setns, unshare, init_module/delete_module, kexec_load, reboot — and, more recently, the io_uring_* family, blocked explicitly due to vulnerabilities that could be exploited to break out of containers. NIST’s recommendation is the conservative one: runtimes ship default profiles that “drop system calls that are unsafe and typically unnecessary for container operation,” and at a minimum you should keep them; custom profiles are for higher-risk apps.

Mandatory access control — the fence around the fence. SELinux and AppArmor add kernel-enforced policy on top: which file paths, processes, and network sockets a container may touch at all. NIST encourages using “the MAC technologies provided by their host OSs in all container deployments,” because they further constrain “the ability of even a compromised container to impact the host or other containers.”

What is isolated — and what is not

Isolated (by default, on a stock Docker host):

  • Process views (PID namespace): you can’t see or signal other containers’ processes.
  • Mount views: each container gets its own filesystem tree; image layers are copy-on-write.
  • Network views: separate interfaces, IP addresses, and routing per container.
  • IPC, UTS (hostname), and user ID views.
  • Resource consumption: CPU, memory, disk I/O metered by cgroups.
  • The syscall surface: narrowed by the default seccomp profile and the capability allowlist.

Not isolated:

  • The kernel itself. Every container on a host shares it, and a kernel or runtime vulnerability is a potential escape route for all of them. NIST calls this out as a first-class host-OS risk, which is why container hosts must be patched on kernel cadence, not app cadence.
  • Kernel modules. Containers can’t load them (capabilities and seccomp block it), but the modules loaded on the host apply to every container — the attack surface is host-wide.
  • Device nodes and privileged mode. As NIST puts it, a container run in privileged mode “has access to all the devices on the host, thus allowing it to essentially act as part of the host OS and impact all other containers running on it.”
  • The host filesystem, when you bind-mount it. Docker’s daemon docs note the stark version: you can share a directory “without limiting the access rights of the container,” and a container whose /host is the host’s / “can alter your host filesystem without any restriction.” NIST adds that containers should “almost never” write to locations controlling host functionality, “e.g., /boot or /etc for Linux containers.”
  • The daemon socket. Anyone who can talk to the Docker daemon effectively has root on the host — the daemon itself “requires root privileges unless you opt-in to rootless mode.” Mounting /var/run/docker.sock into a container is handing over the keys.
  • Hardware side channels. Beyond what any guide can configure away: shared silicon means shared microarchitectural state. This is an inherent property of co-tenancy on one kernel, and it is the practical reason NIST recommends grouping workloads by sensitivity — the mitigation is who you share with, not a flag.

How containers actually get broken

NIST organizes risks by tier — image, registry, orchestrator, container, host OS — and works through concrete threat scenarios: an exploit of a vulnerability within an image, an exploit of the container runtime (the classic “container escape”), and running a poisoned image (malware embedded in an image of unknown provenance). Read together, the attack paths reduce to a short list:

  1. Kernel/runtime exploit from inside — a flaw in the kernel or the runtime software turns a container into a host compromise (NIST’s container risk 3.4.1).
  2. Misconfiguration escape — privileged mode, a widened seccomp profile, sensitive host mounts, or a daemon socket made the isolation layers voluntarily disappear (risk 3.4.3).
  3. Malicious or vulnerable images — embedded malware, embedded clear-text secrets, untrusted base images, stale dependencies (NIST’s image risks, 3.1.x).
  4. Supply chain — a poisoned image or a compromised registry poisoning everything downstream (scenario 5.3).
  5. The compromised app itself — XSS, SQL injection, the boring stuff — which matters precisely because a compromised container inherits whatever network reach its namespaces and policies grant it.

Hardening mapped to each layer

  • Image layer: run as non-root (USER in the Dockerfile — NIST: “images should be configured to run as non-privileged users”); make the root filesystem read-only (“Containers should also be run with their root filesystems in read-only mode”) and mount explicit writable volumes; never bake secrets in — inject them at runtime from the orchestrator’s secret store; never install SSH daemons in containers.
  • Capability layer: drop everything, add back only what the process needs. Docker’s own best practice: “remove all capabilities except those explicitly required.”
  • Seccomp layer: keep the default profile — Docker’s documentation states “It is not recommended to change the default seccomp profile”; never run --security-opt seccomp=unconfined outside a debugging session.
  • MAC layer: leave your distribution’s AppArmor or SELinux policy enabled; don’t disable it to make a deployment “easier.”
  • Host layer: consider a container-specific, minimal host OS (NIST’s attack-surface recommendation); don’t mix containerized and non-containerized workloads on the same host; patch the kernel and runtime urgently; consider user namespaces — supported by the daemon but “available but not enabled by default.”
  • Image supply chain: use only trusted base images, minimal ones; scan images in the pipeline with policy gates; validate signatures; keep registries pruned of stale images.
  • Placement layer: group containers with the same purpose, sensitivity, and threat posture on a single host kernel — the financial database and the public blog do not belong on the same machine, no matter how good the isolation is.

A minimal Compose shape that touches most of these layers:

services:
  api:
    image: registry.example.com/api:1.4.2
    read_only: true
    user: "10001:10001"
    cap_drop:
      - ALL
    cap_add:
      - NET_BIND_SERVICE
    tmpfs:
      - /tmp

The checklist

Before you call a workload “isolated,” verify each item:

  • Runs as a non-root user, with a read-only root filesystem
  • All capabilities dropped except the ones explicitly required
  • Default seccomp profile active; no unconfined anywhere
  • SELinux/AppArmor enabled on the host
  • No privileged containers; no sensitive host paths bind-mounted
  • No daemon socket mounts; rootless mode evaluated where feasible
  • Kernel and runtime patched on an aggressive cadence
  • Images from trusted bases, scanned and signature-validated; no embedded secrets
  • Workloads grouped by sensitivity per host kernel

Isolation in the container model is real, mature, and layered — but it is a bundle of kernel features, not a wall, and every layer has a configuration that quietly removes it. That is the whole lesson of NIST SP 800-190, and it is why hardening is a checklist rather than a checkbox.

Further reading