Docker as a Code Execution Sandbox
Docker's shared kernel leaves untrusted AI code vulnerable to escape despite careful configuration.

Docker is where most engineering teams start when they need to run untrusted code, and for a lot of workloads that instinct is still correct. This piece looks at what happens when that same default gets applied to AI-generated code running at agent scale, where Docker's isolation model holds up, where it quietly gives way, and what a team actually needs once the gap starts to matter.
Docker isolates processes using two Linux kernel features: namespaces, which give each container its own view of the filesystem, process tree, and network stack, and cgroups, which cap and account for resource usage like CPU and memory. That's the whole trick, and it's a genuinely elegant one; it's why Docker won. But the word "isolation" does a lot of quiet work in security conversations, so let's be precise about it. A Docker container has a separate filesystem view and a separate process tree. It does not have a separate kernel. Every container on a host shares the same kernel, the same syscall table, the same drivers as every other container on that box. That single fact is the hinge this piece keeps turning on.
The pattern most teams converge on for running generated code looks nearly identical everywhere you find it: spin up a fresh container from a minimal base image, copy the generated code in, run it and capture stdout, stderr, and any output files, then tear the container down. Five steps, and it's simple enough that it maps cleanly onto tooling engineers already know how to operate. That's not an accident. Docker became the default because it handles runtime version pinning, system dependencies, network surface, and state in one file, and because nearly every open-source agent runtime already ships a Dockerfile or a docker-compose.yml. Nobody has to learn a new primitive. They just reach for what's already sitting on the laptop.
What Docker does not do by default is create a separate kernel, provide hardware-level memory isolation, or restrict most of the syscall surface. Its default seccomp profile blocks a modest slice of available syscalls and leaves a much bigger surface open. So Docker is a specific tool built for a specific job: resource partitioning and reproducible builds among workloads the operator already trusts. What happens when that trust assumption disappears entirely is really the question underneath all of this.
The threat model Docker was not designed for: untrusted, AI-generated code
Docker's design brief, going back to its earliest days, assumed reproducibility and resource partitioning among workloads the operator trusts, in a world where the code inside the container was written by someone on the team. That distinction mattered less five years ago than it does now, because a growing share of the code running in production sandboxes was never written by a person at all.
AI-generated code is untrusted in a way that even junior-developer code isn't, because it comes out of a system that can be manipulated through prompt injection, through tool-chain attacks, through subtle context changes no human reviewer ever sees before the code runs. Per Veracode's 2025 report, nearly half of AI-generated code fails security tests. That's not a rounding error. It means close to half of what these systems produce carries a real vulnerability before it ever touches a sandbox.
So what does the attack surface actually look like? Container escape via a kernel vulnerability or misconfiguration hands an attacker host access rather than just container access, the worst outcome any sandbox can produce. Unrestricted network egress means an AI-generated script, even one that's only accidentally malicious, can exfiltrate data, pull down additional payloads, or pivot into internal systems it was never supposed to touch. Supply-chain attacks through package installs are a direct and growing vector too: agents running pip install or npm install as part of normal operation are trusting a package registry more than most security teams would like, and late-2025 campaigns compromised hundreds of packages specifically targeting agent workflows. Exposed Docker APIs and overly permissive mounts show up repeatedly in model system cards as misconfigurations agents have opportunistically found and used.
The Langflow vulnerability, tracked as CVE-2025-3248, is worth sitting with, because it shows the floor rather than the ceiling. An endpoint in Langflow accepted a raw Python code string and passed it straight to exec(), no authentication, no isolation boundary at all. One unauthenticated HTTP request could produce a reverse shell, exfiltrate files, or move laterally into whatever else sat on that network. CISA added it to the Known Exploited Vulnerabilities catalogue. Docker was never in the picture here, so this isn't a mark against Docker specifically. It's a useful baseline anyway, because it shows what "no sandboxing" actually costs, which puts Docker's partial protections in context instead of leaving them to be judged in a vacuum.
Here's the distinction worth holding onto: Docker treats the container boundary as a resource partition, a way to divide one machine among cooperating workloads. A secure agent sandbox has to treat that same boundary as adversarial, drawn against a workload actively trying, or capable of accidentally trying, to get out. Those are different design problems. No amount of configuration turns one into the other.
Where Docker's isolation actually breaks down under adversarial conditions
The shared kernel is the structural problem, and better configuration doesn't make it go away. If there's a vulnerability in the kernel itself, or a misconfiguration that exposes it, an attacker inside a container can escape to the host no matter how carefully the namespaces were set up. The boundary that matters most sits below the layer Docker actually controls.
This isn't hypothetical. In November 2025, three separate runc vulnerabilities surfaced (CVE-2025-31133, CVE-2025-52565, and CVE-2025-52881), hitting Docker, Kubernetes, containerd, and CRI-O all at once, because all four ultimately lean on the same container runtime primitives. A vulnerability in runc doesn't hit one vendor's product. It hits the shared foundation underneath most of the container ecosystem, simultaneously.
Stack the default seccomp profile on top of that, blocking only a fraction of available syscalls, and you get an attack surface considerably larger than what a hypervisor exposes to a guest VM. Then add the misconfigurations that keep showing up in practice, across teams that ought to know better by now: containers run with --privileged, host filesystem paths mounted straight into the container, the Docker socket left reachable from inside the very container it's supposed to manage, unrestricted outbound network left on by default. None of this is exotic. It happens because Docker's defaults optimize for developer convenience, and when something breaks at 2am, the fastest fix is usually the one that widens the permission boundary rather than the one that respects it. Reaching for --privileged because a build script won't run otherwise is an extremely human response to an extremely common problem.
Can hardening close the gap? Partially, and the line matters. Careful configuration, custom seccomp and AppArmor profiles, dropped capabilities, read-only mounts, meaningfully reduces excessive privilege, blocks writable host access, and shrinks the syscall surface a container can touch. What hardening cannot do is eliminate the shared-kernel problem itself; you can shrink the exposure, but you can't remove the architectural constraint underneath it. Hardening isn't free either. It adds real operational complexity, and it tends to break assumptions baked into common base images that were never built with those restrictions in mind.
Hardened Docker is meaningfully better than Docker out of the box. It is still a different class of isolation than a microVM or a gVisor-based sandbox provides, and teams need to figure out, honestly, which class of guarantee their actual threat model calls for, rather than assuming more configuration flags equal more security.
How stronger isolation technologies address what Docker cannot
Two approaches actually eliminate the shared-kernel problem instead of just narrowing it, and they get there by different paths.
MicroVMs, the category that includes Firecracker and Kata Containers, give each workload its own dedicated kernel. That's a hardware-enforced boundary rather than a software convention, so a compromise inside one workload's kernel doesn't reach the host or any neighboring workload. Firecracker in particular was built around a minimal attack surface: its VMM runs tens of thousands of lines of Rust, against the far larger C codebase behind a general-purpose hypervisor like QEMU. Fewer lines of code in the trust boundary means fewer places for a bug to hide, and Firecracker boots fast enough to be practical for agent workloads, which wasn't true of traditional VMs a decade ago.
gVisor takes a different route. It intercepts syscalls in user space through an application kernel that handles most of a workload's syscalls itself, rather than passing them straight through to the host kernel. That reduces host kernel exposure without eliminating it. It's a real improvement over standard containers and a reasonable fit for compute workloads where a team has some say over what code runs. But the consensus among people who study this closely is that microVMs offer a stronger guarantee for code that's fully untrusted, which is exactly the bucket AI-generated code sits in.
Put plainly: Firecracker or Kata microVMs for the strongest isolation, gVisor as a reasonable fallback depending on the threat model and performance needs, standard containers as insufficient on their own for production execution of untrusted AI-generated code.
One layer matters no matter which isolation technology a team picks: network access. It's the single highest-risk capability you can hand to code you don't trust, the difference between a contained mistake and a real breach. The sane default is no network access at all, with teams opting in explicitly to only the connections a workload actually needs. This holds at every layer of the stack. Even a microVM with open network access carries meaningful risk, because the strength of the kernel boundary doesn't buy you much if the workload can just phone home anyway.
Isolation is one layer of a bigger defense, not the whole thing. Input sanitization, static analysis of generated code before it runs, pre-execution checks, runtime monitoring while it's running, and post-execution review afterward all sit alongside the isolation boundary. Teams that treat isolation as the entire solution tend to find out otherwise, usually at a bad time.
Which raises the obvious tension the next section has to sit with: stronger isolation costs something. It adds startup latency and operational complexity, and at agent scale, latency stops being a minor inconvenience fast.
Startup latency and why it compounds at agent scale
Agents don't execute code once and stop. A typical agent workflow spins up an environment, runs a step, captures the output, and often spins up again for the next step, sometimes dozens of times inside a single task. Whatever latency shows up at each spin-up gets paid over and over, compounding across the whole chain of tool calls.
Docker's cold-start time for a pre-pulled image is fast by the standards of traditional infrastructure. Pull a new image, though, or initialize anything past a bare-bones environment, and the latency adds up fast enough to break the responsiveness an agent needs to feel usable. A human waiting three seconds for a terminal command barely notices. An agent running twenty sequential tool calls, each tacking on a few seconds of container startup, notices immediately.
The scale involved isn't small anymore either. The market for sandbox execution went from tens of thousands of runs a month in early 2024 to roughly 15 million runs a month by March 2025. At that volume, a few hundred milliseconds of extra latency per run stops being a rounding error and starts showing up directly in aggregate throughput, and in how responsive the product actually feels to whoever's waiting on the other end.
So how do you shrink startup time without giving up the isolation gains? A cold Firecracker boot is fast enough for plenty of use cases, but it's measurably slower than a pre-warmed setup, and that difference matters at scale. The technique that closes the gap is snapshot-restore: boot a microVM once, freeze its state, restore from that frozen snapshot instead of booting cold every single time. Some purpose-built sandbox platforms report sub-90ms cold starts using pre-warmed sandbox pools built on this approach, with some configurations reaching as low as 27ms. Warm pool management, keeping a standing supply of ready-to-use sandboxes instead of building one from scratch on demand, is what separates purpose-built sandbox infrastructure from a naive "spin up a container when you need one" approach.
Why don't generic cloud functions just solve this? Lambda and Cloud Run are mature, well-built platforms, but neither was designed around the agent execution pattern. Lambda resets state between invocations by design, which cuts directly against what a multi-step agent workflow needs. Cloud Run doesn't reliably guarantee the sub-200ms cold starts agent responsiveness calls for. Neither preserves filesystem state across the sequence of tool calls an agent makes over a task.
Fast, secure startup isn't impossible. It just takes real architectural investment, pre-warming, snapshotting, pool management, none of which Docker gives you out of the box, and none of which is trivial to build correctly on your own.
The statefulness problem Docker's ephemeral model wasn't built to solve
Docker's canonical pattern, spin up, execute, destroy, is ephemeral by design. Nothing survives the container's lifecycle unless a team goes out of its way to make it survive. For isolated, single-step code execution, short analysis tasks, one-off scripts, that model works well and buys a genuine security benefit: nothing persists, so nothing accumulates for an attacker to exploit later.
But that's not how coding agents actually work, and the mismatch shows up fast once you build past a demo. Installed packages need to persist between steps; reinstalling a project's entire dependency tree on every tool call is far too slow to be usable. Intermediate files, partial results, editor buffers, all of it needs to carry across tool calls within a single agent task, or the agent effectively loses track of what it's already done. And long-running work, a multi-hour research task, a full test suite, a build pipeline, can't safely get interrupted and restarted from zero without losing real progress.
That's the core tension, worth stating plainly rather than glossing past: ephemeral sandboxes are the cleanest security model available precisely because nothing sticks around to go wrong later. Stateful environments are what coding agents and research workflows actually need in order to be useful. Those two requirements pull in opposite directions, and no amount of clever engineering makes that tension disappear; it just gets managed better or worse.
Docker's existing answer is the volume mount, a reasonable partial fix with real costs attached. A volume mounted to the host filesystem reintroduces exactly the isolation questions the sandbox was supposed to resolve in the first place. Volumes need external management, they don't travel with the container image, and they do nothing about the harder problem of resuming a running agent mid-task, where what needs preserving isn't just files on disk but the live state of a process.
Checkpoint and restore is the more complete answer architecturally. The idea: snapshot both the in-memory state and the filesystem state of a running sandbox, store that snapshot, restore it later so the agent picks up exactly where it left off, mid-computation if it has to. This is converging into something close to a standard feature across purpose-built sandbox platforms, for good reason. Docker doesn't provide it natively; building it means reaching for external tooling like CRIU, plus careful orchestration to make the restore reliable and fast.
Statefulness and security are in tension by design, not by accident. Teams that handle this well don't try to collapse the two into one layer; they keep the persistence layer separate from the isolation boundary, so preserving an agent's progress doesn't mean weakening the wall around it.
What purpose-built sandbox infrastructure actually provides that Docker alone does not
None of the three gaps covered so far, isolation, latency, statefulness, sit apart from each other. They interact, and that interaction is really what explains why purpose-built infrastructure exists as its own category instead of just being "Docker with extra steps."
Stronger isolation through microVMs adds startup latency, which pushes a team toward warm pools and snapshot-restore to claw that speed back. But snapshot-restore, once built, doesn't just fix latency; it happens to enable stateful resumption of long-running tasks too, since a snapshot is functionally a saved state. And the statefulness requirement itself pushes further toward dedicated kernels rather than shared-kernel containers, because state persisting across a multi-tenant shared kernel is state that could, in principle, leak between tenants. Solve one of these the right way and you're most of the way to solving the other two.
A rough taxonomy has settled out of this. At one end sit the primitives, Firecracker and gVisor themselves, which hand a team maximum control but require running your own fleet and your own scheduler, a real undertaking that makes sense for organizations with dedicated infrastructure teams and a reason to own that layer. At the other end sit managed platforms, which handle the orchestration, the warm pools, and the compliance work, aimed at teams trying to go from prototype to production without standing up infrastructure from scratch. In between, for teams already on Kubernetes, GKE's Agent Sandbox offers open-source sandboxed Python environments at no extra charge, a reasonable on-ramp for that specific ecosystem.
Enterprise teams tend to need more than the raw technical isolation properties, and it's worth naming what that "more" actually is. Customer-managed compute, so a company keeps data and cost control inside its own cloud environment rather than routing untrusted code execution through someone else's infrastructure by default. Compliance certifications, SOC 2, HIPAA, GDPR, that function as table stakes for procurement rather than a nice-to-have checkbox. Docker-native compatibility, so a team doesn't have to rewrite an existing stack just to get stronger isolation underneath it. An open-source, auditable design a security team can verify directly instead of taking entirely on faith.
Daytona is a useful concrete example of what this looks like assembled into a single product: sub-90ms cold starts through pre-warmed pools, statefulness built in from the start through checkpoint and restore rather than bolted on afterward, Docker-native compatibility so existing workflows carry over, and SOC 2, HIPAA, and GDPR compliance alongside customer-managed compute and an open-source codebase. It's built specifically around AI-generated code execution rather than retrofitted from general developer tooling, and its customer list, LangChain, n8n, Clay, and Parabola among them, reflects teams that ran straight into exactly the gaps this piece has been walking through.
Docker works fine as a starting point. The gap tends to surface later, once a team is running untrusted, AI-generated code in production at real scale.
How to read your own requirements against these gaps when making an infrastructure decision
The decision in front of most teams isn't Docker versus everything else. It's a narrower, more honest set of questions about what a specific workload actually needs, and working through them in order beats jumping straight to a vendor comparison.
How adversarial is the code you're actually running? Code generated by a model your team fully controls, with tight guardrails and no external tool access, carries a different risk profile than code an agent writes in response to content it pulled off the open internet, where prompt injection is a live possibility rather than a theoretical one. The more adversarial the input, the less academic the shared-kernel question from the second section gets.
What does your latency budget actually look like? A single-step, occasional code execution feature can tolerate a Docker cold start measured in seconds without anyone noticing. An agent chaining together dozens of tool calls per task cannot, and the math from the latency section, tens of thousands of runs a month scaling to roughly 15 million, isn't a hypothetical ceiling. It already happened.
Does your workload need state to persist across steps, or is each execution genuinely self-contained? If an agent installs a dependency once and reuses it across the next ten tool calls, or needs to resume a long-running task after an interruption, the ephemeral Docker pattern is going to fight you the whole way, and checkpoint-restore stops being a nice-to-have.
And what does your compliance and operational surface actually require? A team with a dedicated infrastructure group and specific regulatory needs might reasonably build on Firecracker directly. A team trying to ship a product without also becoming an infrastructure company is usually better served by a managed platform that's already solved the orchestration problem.
None of these questions has one correct answer for every team, and that's the point of laying them out this way instead of handing over a verdict. Docker got teams most of the way here because it's familiar, well-documented, and genuinely good at the job it was built for. The gap only shows up once the code being executed is untrusted, the scale is real, and the workflow needs to remember what it did five steps ago. Whether that gap matters for your system is something only you can answer, but at least now you know where to go looking for it.


