Agent in Production

Hardening Container Images for Coding Agent Runtimes

AI-generated code demands tighter container defenses than human-written code ever did.

Features Editor · · 15 min read
Cover illustration for “Hardening Container Images for Coding Agent Runtimes”
Sandbox Design · September 18, 2026 · 15 min read · 3,297 words

Coding agents no longer just autocomplete a function and wait for a human to hit enter. They run full loops now: reading a spec, writing code, executing it, checking the result, and iterating again, often dozens of times before a person looks at the output. That shift changes what a container has to defend against, because the code running inside it wasn't written by a person anyone vetted. It was generated on the fly, by a model, and the container was never built with that threat model in mind.

What containers were built for versus what they're running now

The jump in capability behind this shift is not subtle. On SWE-bench Verified, the benchmark that's become the industry's yardstick for agentic coding, top agents were solving roughly 4% of problems on SWE-bench Verified in 2023. By 2026, leading agents were clearing 70 to 90% of the same benchmark problems. That's a change in what these systems are trusted to do unsupervised, and it directly expands what an agent might attempt inside the container it's running in.

Containers, as a technology, were designed around a much calmer assumption: a human wrote the code, tested it, and pushed a predictable, versioned artifact into a predictable runtime. AI-generated code violates that assumption on every axis. It's non-deterministic (the same prompt can produce different code on different runs), it's untrusted (nobody reviewed it line by line before execution), and it can be adversarially shaped, whether through a poisoned dependency, a manipulated prompt, or a model that's simply confused about what "safe" means in context.

Security research frames the elevated risk for AI workloads in two parts. First, the attack surface itself grows: model-serving code, inference runtimes, tokenizers, and model-hub integrations all get added to the image, and each one is a new thing that can be misconfigured or exploited. Second, the impact of a breach gets amplified. A compromised static web service is bad. An agent with broad filesystem and network permissions, executing code that wasn't reviewed by anyone, can do considerably more damage in considerably less time.

Security leaders know this. In the CSA's State of AI Cybersecurity 2026 survey of more than 1,500 security leaders, 92% said they were concerned about the security implications of AI agents. But concern hasn't translated into coverage: most of those same organizations report real gaps in how comprehensively they govern AI security in practice. And agent-to-agent interactions remain broadly under-monitored in practice, with prompts, tool calls, and outputs rarely governed as a unified whole.

None of this gets fixed with a single control. Hardening a container for agent workloads means building a stack, layer by layer, starting from the image itself and ending with the logs that let a security team reconstruct what happened after the fact. The rest of this piece walks that stack from the bottom up.

Diagram: Agent Capability Leap: 4% to 70–90% on SWE-bench Verified. Visualizes: Visualize the dramatic benchmark improvement of top coding agents on SWE-bench Verified: roughly 4% of problems solved in 2023, rising to 70–90% by 2026.

Minimalism and immutability as the architectural foundation

Minimalism, stated operationally, means every package, every library, every shell utility that ships in the final image has to earn its place. If it's not strictly necessary for the agent to do its job, it's attack surface sitting idle, waiting for someone (or something) to find a use for it. A stray copy of curl or a forgotten package manager doesn't help the agent write code. It just gives an attacker, human or automated, one more tool to work with after a compromise.

Immutability is the companion principle. Once an image is built, signed, and deployed, it doesn't change. Not for a hotfix, not for a config tweak, not for anything. Any update, however small, triggers a full rebuild and a full re-validation. That discipline sounds bureaucratic until you consider the alternative: a production container that's been patched in place has no clean record of what it currently contains, and no guarantee that the patch didn't introduce something unintended.

Hardening guidance breaks the architecture into three layers, and each one needs separate attention.

The base layer is the operating system, and the guidance here is blunt: move away from general-purpose distributions like Ubuntu or CentOS for the actual runtime. They carry package managers, shells, and utilities that a production agent container has no legitimate reason to use. Distroless images, or a stripped-down Alpine base, remove that baggage entirely: no shell, no package manager, nothing an attacker can use to explore or persist after landing inside.

The runtime layer covers dependencies: the AI framework itself, plus pinned language libraries, ideally isolated in virtual environments during the build so version drift doesn't sneak in between builds.

The security policy layer is the one teams skip most often, and it's also the last line of defense. Seccomp profiles, AppArmor profiles, Kubernetes SecurityContexts, these are the controls that decide what a process is actually allowed to do at the kernel level, regardless of what the application code thinks it's allowed to do. Teams pour effort into model quality and prompt engineering, and comparatively little into asking a simpler question: what syscalls does this agent runtime actually need to make, and why does it currently have permission to make many more than that?

GPU workloads add one more wrinkle. CUDA libraries and drivers have to be version-locked to both the base OS and the ML framework running on top of them. A mismatch here doesn't just cause instability, it opens security gaps, because a driver version the security team never tested is now running in production.

Minimalism and immutability are principles. The multi-stage build configuration is the mechanism that actually enforces them.

Building the image: multi-stage Dockerfiles that separate build surface from runtime surface

A multi-stage build lets a team use a large, tool-heavy builder image, full of compilers, dev headers, and whatever else compilation requires, without any of that ever reaching production. The builder produces artifacts. Those artifacts get copied into a small, hardened runtime stage, and the builder image itself is discarded. The build surface never ships.

For agent images specifically, a three-layer separation model works well in practice. A base image stage handles system dependencies and the Python environment. A model layer stage holds model weights or API configuration, with everything pinned to a specific version. A configuration layer stage carries the agent's operational instructions, including files like AGENTS.md, a settings.json, and tool definitions.

The payoff of splitting things this way is operational. A new prompt template or an updated tool definition can go out without touching the model layer at all, and each layer can be rolled back independently if something goes wrong. That's a meaningfully faster recovery path than rebuilding one monolithic image every time a config file changes.

The runtime stage itself should target something like gcr.io/distroless/python3-debian13, or scratch if the workload allows it. Both remove the shell, the package manager, and any utility an attacker could repurpose after breaking in.

Docker Hardened Images, or DHI, give teams a practical baseline for that runtime stage rather than requiring everyone to hand-roll their own. Docker's figures on this are specific: DHI reduces vulnerabilities by up to 95% compared to community images built the conventional way. In Docker's own Scout comparison, a hardened Python image dropped from 412MB down to 35MB, cut known CVEs to zero, and removed more than 500 unnecessary packages in the process. Every DHI image ships with a full software bill of materials, transparent and public CVE data, SLSA Build Level 3 provenance, and cryptographic proof of authenticity, so a team downstream isn't taking the image's cleanliness on faith. As of December 2025, more than 1,000 of these images were made free and open source under the Apache 2.0 license, built on Debian and Alpine bases. Docker has extended the same approach to MCP server images, with more than ten popular servers, including Grafana and MongoDB, covered at launch, bringing the same minimal-footprint, provenance-backed treatment to the tool infrastructure agents actually call.

Teams that try to build this hardening themselves, without a baseline like DHI, tend to run into the same handful of problems. ML frameworks quietly depend on specific libc variants, CA certificate bundles, or locale files that never appear as an explicit dependency, and they fail silently at runtime once they're missing. Minimal images also remove the shells and utilities teams are used to accessing remotely for debugging. Observability has to be built properly up front rather than treated as something you can always just work around later. And GPU dependency mismatches that a bloated image accidentally papered over become visible, and blocking, the moment the image gets stripped down.

Signing closes the loop on the supply chain. An image can be minimal, immutable, and beautifully layered, and still be swapped out for a tampered copy somewhere between build and deploy. Validating signatures at deploy time is what stops that substitution from ever reaching the runtime.

Locking down the syscall surface with seccomp and capability dropping

Even a genuinely minimal image can be exploited if the container running it has unrestricted access to the kernel's syscall interface. That's the gap this layer closes. An agent executing adversarial or simply buggy code doesn't need a vulnerable package to cause damage, it needs an open door at the kernel level, and syscalls it was never going to legitimately need are exactly that door.

A handful of Kubernetes SecurityContext settings do most of the work here. runAsNonRoot: true limits what an attacker can touch on the filesystem and at the kernel interface if they get code execution. readOnlyRootFilesystem: true stops the agent from writing to its own image layer while it's running. allowPrivilegeEscalation: false closes off one of the more common privilege-escalation paths. Dropping all Linux capabilities by default, then adding back only the small number the agent actually needs, replaces a broad grant of trust with a short, reviewable list. And applying the RuntimeDefault seccomp profile blocks the long tail of syscalls that a containerized workload essentially never has a legitimate reason to invoke.

AppArmor profiles sit alongside these controls rather than replacing them, defining which files, directories, and network operations the agent process can touch, independent of whatever the Linux user model says. Between seccomp, capability drops, and AppArmor, the goal is the same throughout: grant back only what's explicitly needed, document why, and review it, instead of handing out broad permissions and hoping the agent stays inside the lines.

This matters more for AI-generated code than for almost anything else running in a container today, because the agent can generate and execute operations nobody anticipated when the container was first configured. Prompt engineering can tell a model what it should do. It cannot enforce what a process is kernel-level permitted to do. Syscall filtering is the boundary that holds regardless of what the model decides to try.

Why shared-kernel containers are insufficient for untrusted agent code

By early 2026, the consensus among people building this infrastructure had settled on a fairly stark point: standard containers built on shared-kernel isolation, meaning Docker or runc in their default form, aren't sufficient for running untrusted AI-generated code. A shared kernel means a container escape doesn't just compromise one workload, it hands an attacker a path toward the host and everything else sharing that kernel. For code nobody reviewed before it ran, that blast radius is too large to accept as a baseline.

Three approaches have emerged to address it, each with its own trade-offs.

MicroVMs give each workload a dedicated kernel, which is the strongest isolation boundary available short of physically separate hardware. Firecracker, a microVM technology, boots in around 125 milliseconds and provides hardware-level virtualization at a fraction of the overhead of a traditional VM, though it adds real operational complexity compared to a plain container. Kata Containers takes a different integration path, plugging into Kubernetes through the Container Runtime Interface via a RuntimeClass, and it automatically provisions a microVM for any pod that requests the Kata RuntimeClass. That gives teams VM-level isolation while keeping standard Kubernetes APIs intact, rather than requiring a separate operational model.

gVisor takes a different approach entirely: a user-space kernel intercepts syscalls before they reach the real one, giving something close to VM-level security at container-level overhead. It's generally easier to integrate than a full microVM setup, though it comes with its own performance and compatibility trade-offs that teams need to test against their actual workload rather than assume away.

Rootless and daemonless engines, Podman being the notable example, remove the root daemon from the equation. Without a privileged daemon running in the background, a compromised container process has one less escalation path available to it, which makes this class of engine well suited to running agent-generated code that hasn't been vetted.

Docker's own answer, introduced in 2026, is Docker Sandboxes: each coding agent gets a disposable microVM with its own private filesystem, its own Docker daemon, and an isolated network stack, all managed through the sbx CLI. Network policy gets chosen at first login, Open, Balanced, or Locked Down, and credentials like a GitHub token get scoped to that specific sandbox session through sbx secret set rather than pasted into the session or left sitting in an environment variable. The sandbox is destroyed after use. It's disposable by design, not something meant to persist.

Teams running agents outside a managed platform are increasingly finding sandboxing built into the CLI tools themselves. OpenAI's Codex CLI defaults to sandboxing, using Seatbelt on macOS and a combination of bubblewrap, Landlock, and seccomp on Linux, with three access modes ranging from read-only up to a "danger-full-access" setting that has to be chosen deliberately. Claude Code runs its bash tool inside a sandbox backed by Seatbelt or bubblewrap plus a network proxy, and its cloud sessions run inside an isolated, Anthropic-managed virtual machine.

For teams building agent execution directly into their own infrastructure rather than relying on a CLI's defaults, a set of embeddable and managed sandbox platforms has emerged to fill that need, including microsandbox for self-hosted deployments, Daytona for self-hostable git-based environments, and the CodeSandbox SDK, which provides forked, snapshotted microVM environments built for collaborative agent execution.

There's also an accelerator-specific angle developing here. NVIDIA announced OpenShell at GTC 2026, an open-source sandboxed runtime for autonomous agents that wraps them in kernel-level isolation governed by declarative YAML security policies. Its arrival signals that isolation on the accelerator side of the stack is becoming its own distinct concern, separate from general container isolation, rather than something teams can assume the existing tooling already covers.

Isolation alone isn't the whole story, though. Agents can burn through CPU, memory, disk, and network, whether by accident or by design, and hard resource limits need to be enforced at the sandbox or Kubernetes level. Trusting the agent to police its own resource use defeats the purpose of building the sandbox.

Scoped credentials and secrets management inside agent sandboxes

The most common failure mode here is a hardcoded API key sitting in an agent's configuration file, an environment variable that outlives the session it was meant for, or a credential with more scope than the specific task ever required. It's a hardcoded API key sitting in an agent's configuration file, an environment variable that outlives the session it was meant for, or a credential with more scope than the specific task ever required. Any one of those turns what should have been a contained sandbox breach into a full credential compromise, because the attacker doesn't need to break out of the sandbox, they just need to read what's already sitting inside it.

The fix is a per-session credential model. Credentials get minted fresh for each agent session, scoped tightly to what that session actually needs, and revoked the moment the session ends. Nothing gets shared across sessions, and nothing gets reused just because it's convenient.

In practice, that means never baking secrets into the image itself, since anything baked into a layer persists there and becomes visible to anyone with pull access to the registry. It means injecting credentials at runtime, through a proper secrets mechanism like docker pass or sbx secret set, rather than an environment variable that lingers in shell history or container metadata long after it's useful. And it means scoping IAM roles and API tokens down to the minimum permission set the specific task requires, rather than the broader role that happens to be sitting around and convenient to reuse.

MCP servers add a new wrinkle to this picture, because they're a new credential surface that didn't exist in quite this form before agents started calling tools directly. Docker's MCP Gateway addresses it by running each MCP server as its own isolated container and routing all requests through a single control point, instead of every AI client independently configuring its own MCP server with its own separate set of credentials. That centralizes credential governance for the whole tool layer, rather than leaving it scattered across however many integrations a given agent happens to use.

Regulation is starting to make this concrete rather than optional. The EU AI Act becomes enforceable on August 2, 2026, and under its cybersecurity and logging mandates, any action layer an agent touches, internal services, third-party platforms, MCP servers, all of it, falls in scope. Recitals 99 and 100 extend that compliance boundary across every agent in a multi-agent chain that performs a high-risk function, so a credential scoping failure two hops down a chain of agents doesn't stay contained to that one hop.

None of this is auditable, though, unless every call that uses a scoped credential gets logged somewhere a human can actually review it later.

Runtime observability and audit logging as a hardening requirement, not an afterthought

Diagram: The Agent Security Blind Spot: What Organizations Actually Monitor. Visualizes: Visualize the monitoring gap revealed by the EY and AIUC-1 Consortium survey: only 17% of organizations continuously monitor agent-to-agent interactions, 80%…

The EY and AIUC-1 Consortium survey put a number on the size of this blind spot: only a minority of organizations monitor AI traffic end-to-end, across prompts, tool calls, and outputs together, and only 17% continuously monitor agent-to-agent interactions. That means most teams running agents in production cannot fully reconstruct what those agents actually did, not after the fact and often not in real time either.

The same survey found that 80% of organizations had documented risky agent behaviors, including unauthorized system access and data exposure. Those behaviors happened. The gap isn't that agents are behaving badly at some hypothetical rate, it's that most organizations lack the visibility to catch it quickly when it happens, which is precisely what the monitoring numbers above would predict.

Closing that gap means logging a specific set of things at the runtime layer, beyond generic application logs. Every tool call the agent makes needs to be captured with its full request and response. Every diff the agent produces needs to be attributed back to the session and the trigger that caused it. Thinking tokens and reasoning traces, where the model exposes them, need to be retained to support a post-hoc review of why the agent made the decision it made. Credential usage events need their own record, showing which scoped credential was used in which specific call. And resource consumption per session, CPU, memory, network, and API spend, needs to be tracked as its own signal, both for security and for cost control.

This is where the earlier architectural choices come full circle. A minimal, distroless runtime with no shell is a genuine security win, but it also means there's no shelling into a compromised container after the fact to see what happened. There's no forensic investigation to run against a filesystem that was never there to begin with. The only evidence available after an incident is whatever got logged before it. Treating observability as a bolt-on, something to add once the hardening is "done," gets the priority backwards: the logging has to be built in from the same point the image is, or the rest of the hardening stack is defending a system nobody can actually investigate when something finally slips through.

Sources

  1. Docker Makes Hardened Images Free, Open and Transparent for Everyone | DEVOPSdigest
  2. What's New in Docker in 2026: Sandboxes, Hardened Images, and the AI-Native Container Platform - Collabnix
Filed underSandbox Design

More in Sandbox Design