Chapter · containers

Containers: Docker, Podman and the shared kernel

Containers are the default answer for local agent isolation, and they deserve to be. They are also the most commonly misunderstood boundary in the field. This chapter is about what a container really gives you, what quietly removes it, and when to stop using one.

What a container actually is

A container is a process — or a small tree of processes — with four things applied to it:

  • Namespaces change what it can see: a private PID tree, its own mount table, its own network stack, its own IPC, UTS and cgroup view, and usually a user namespace that remaps UIDs.
  • cgroups change what it can consume: CPU, memory, PIDs, I/O.
  • seccomp filters which syscalls reach the kernel. Docker's default profile unconditionally allows hundreds of syscalls and denies a list of dangerous ones.
  • LSMs — SELinux, AppArmor, and the container's own capabilities — constrain what it may do to the objects it can already see.

Notice what is missing from that list: a kernel. The container calls into the same kernel as your desktop session. Everything above is a view-and-permission construct inside that kernel, so a bug in the kernel, in the runtime, or in the container's configuration is not contained by the container.

That is not a reason to avoid containers. It is a reason to be precise about what you are claiming when you say you are sandboxed.

CONTAINERS agent lane 1 agent lane 2 namespaces only MICROVM lane 1 own kernel lane 2 own kernel HOST KERNEL one copy. a bug here is a host bug. YOUR FILES · YOUR KEYS · YOUR SOCKETS bind mounts decide how much of this the agent sees
Two containers and two microVMs on the same laptop. The containers reduce what the agent can see; the microVMs also stop it getting to the same kernel.

The five mistakes that remove the boundary

Every one of these is common in published quickstarts, and each one converts a container into something much closer to a shell on your machine.

MistakeWhat it hands overWhat to do instead
Mounting the container socket
-v /var/run/docker.sock:…
Root on the host. Anything that can call the socket can start a privileged container with your filesystem mounted. Do not. If a tool needs to build images, use rootless BuildKit or a prebuilt image.
--privileged Every capability, all devices, no seccomp default, no AppArmor, user namespaces become incompatible. Grant the single capability you need, or pass the one device you need.
Mounting your home directory Every credential, dotfile, SSH key, browser profile and shell history you have. Mount the project directory only, and read-only for the parts the agent should not write.
--network host Reachability to everything on your LAN, your cloud metadata endpoint and any service bound to localhost. Use a bridge network, and an --internal network when you need a hard egress wall.
Passing secrets by environment variable Every process in the container, every crash dump, every docker inspect, and anything the agent can convince to print its environment. Broker them at the boundary. See Secrets.
The incident worth knowing about In May 2026 a security vendor documented what it described as the first case of an LLM harness — not a human — autonomously performing a container escape and credential replay: the agent called the Docker socket API, created a privileged container with a host bind mount, read /etc/shadow and SSH keys, and replayed a Kubernetes service-account token to dump every secret in a namespace. The only precondition was that the socket was mounted. Whatever you believe about agent capability, assume the agent will use the affordances you left lying around.

Docker, Podman, and what is actually different

Docker

  • Architecture: a persistent root-owned daemon (dockerd) reachable over a Unix socket. Rootless mode exists and uses rootlesskit per user.
  • The real risk: the socket is a root-equivalent control surface. Docker's own documentation treats membership of the docker group that way.
  • Why people use it: the ecosystem. Every tutorial, every CI system, every dev container spec, every agent tool.
  • Verdict: excellent engine, dangerous defaults. Audit whatever template you copied.

Podman

  • Architecture: daemonless; each podman run forks the runtime directly. Rootless by default, with container root mapped to an unprivileged host UID.
  • The real risk: the same shared kernel. Plus rootless networking quirks — slirp4netns/pasta are user-space stacks with their own history.
  • Why people use it: smaller root surface, the same CLI, and on Windows and macOS podman machine gives you a real VM boundary for free.
  • Verdict: the better default for a new agent setup, and the one this guide recommends.
DOCKER dockerd (root) /var/run/docker.sock anyone in the docker group ≈ root container PODMAN (LINUX) you → crun/runc → container no daemon, rootless by default escape lands as an unprivileged user container PODMAN ON WINDOWS Windows host WSL2 / Hyper-V VM ← a real boundary containers (shared Linux kernel) On every platform: containers share one kernel. That is the whole story, and it does not change.
The architectural difference that matters is not the CLI, it is whether there is a root daemon with a socket. The Windows arrangement is quietly one of the better container stories available, because there is a hypervisor underneath whether you asked for one or not.

Containers that stretch the model

Sysbox

An OCI runtime that lets a container run systemd, Docker-in-Docker and nested user namespaces without --privileged. It is how you run a "real machine" feel inside a container. Useful, and it deliberately widens what the container can do — so pair it with a VM if you need a hard boundary.

Incus and LXC

System containers: a full init, users, services, closer to a small machine than an application container — but still containers, so they share the host kernel and do not use KVM. Incus also does real VMs, from the same CLI and profile system, and those are the instances that get their own kernel; it is the best power-user tool for a pool of persistent agent machines, as long as you are clear about which of the two you are running. Snapshots and clones are near-free on a copy-on-write storage backend.

Dev containers

A standard (devcontainer.json) for describing a container-based dev environment. Agent tooling has converged on it because templates are shareable and reviewable. Trail of Bits publishes a hardened devcontainer for running Claude Code in bypass mode, which is one of the few agent-sandbox artefacts with a real security review behind it.

Where the boundary actually is for dev containers A dev container is a container. If the template mounts your home directory, forwards the Docker socket for convenience, or runs with extra capabilities, the isolation you think you have is not there. Read the template the way you would read a firewall rule.

Using containers well

Baseline hardening

podman run --rm -it \
  --user 1000:1000 \
  --cap-drop=ALL \
  --security-opt=no-new-privileges \
  --read-only --tmpfs /tmp \
  --memory=2g --pids-limit=512 --cpus=2 \
  --network=none \
  --mount type=bind,src=$PWD,dst=/work,ro \
  -v agent-work:/work-rw \
  localhost/agent-image:latest

Read-only root filesystem, read-only project, a writable named volume for the actual work, no network, no capabilities, and limits that stop a fork bomb. This is the shape to start from.

The habit that matters more than flags

Do not give the agent your project. Give it a copy, and make "keep the changes" a separate reviewed operation on the host. This is the single biggest behavioural change available inside the container world, and it is nearly free: a Git worktree or a reflink copy costs milliseconds.

It also fixes the failure mode that gets people: a container with a wide bind mount will happily delete your files, and unlike a VM there is no second copy anywhere.

How to do it cheaply →

When to stop using containers When your threat model includes code that is actively trying to leave, or when the workload is multi-tenant, or when you are running an agent unsupervised against untrusted content with network access. At that point the honest options are a microVM, gVisor, or a real VM — and the rest of this guide is about which one.