Give Your AI Agent a Sandbox: Disposable Docker Isolation in Practice A developer demonstrates how to isolate AI agents in disposable Docker containers to prevent accidental damage and untrusted dependencies from affecting the host machine. The approach uses a minimal sandbox image with a non-root user and enforces resource limits and timeouts, arguing that containers provide the right balance of isolation and performance for agent workloads. A container is a process with boundaries. It shares the host kernel, but it cannot see the host's files, processes, or network sockets unless you explicitly hand them over. That property — isolated by default, exposed by request — is exactly the contract an AI agent needs when it runs code on your machine. Agents are getting good enough to be useful, which means they are getting dangerous enough to be contained. This article walks through why a bare shell is not a boundary, where containers sit on the isolation spectrum, and how to wrap an agent's work in a disposable Docker sandbox without turning your deployment into an orchestration project. An agent that executes commands on your host gets the full surface of your machine. Every file the agent writes lands in your real filesystem. Every process it spawns can see your environment variables, and environment variables are where secrets live. Every port it opens is a port on your network. None of this requires malice. A model that writes a loop with a wrong exit condition can fill your disk. A tool that misparses a path can overwrite a file it was only supposed to read. A dependency pulled from a registry can ship with a post-install hook, and the agent will happily run it in your shell. The failure mode is not exotic. The same properties that make an agent useful — it can read, write, install, and execute — are the properties that make it uncontained. The fix is not to trust the agent less; it is to change the environment the agent acts in, so that even a fully autonomous run has a hard ceiling on what it can touch. Isolation is a spectrum, and each rung buys a different boundary at a different price. A virtual environment isolates Python packages. It does not isolate files, processes, or network access. A dependency that wants to write to $HOME still can. A subprocess that wants to bind a port still can. Venvs solve dependency conflicts, not containment. A container isolates all three. It gets its own filesystem view, its own process tree, its own network namespace. It shares the kernel with the host, which makes it cheap to start and cheap to throw away. That last property is the one that matters for agents: a container is designed to be destroyed, and destroying it removes every trace of what ran inside. A virtual machine isolates the kernel too. That is stronger, but it costs minutes of boot time, gigabytes of memory, and a layer of management tooling. For most agent workloads, the extra boundary is not worth the weight. The threat model for an agent run is accidental damage and untrusted dependencies, not a hostile kernel exploit. The pragmatic default sits in the middle: run the agent in a container, mount in only the inputs it needs, mount out only the outputs it produced, and delete the container when the run ends. A sandbox image does not need to be complicated. The essentials are a base image, a working directory, and a non-root user. Running as root inside a container is a common mistake: root in the container is still root for anything the container is allowed to do, and the whole point of the exercise is limiting what a run can do. FROM python:3.12-slim RUN useradd --create-home --uid 1000 agent WORKDIR /work COPY --chown=agent:agent . /work USER agent ENTRYPOINT "python", "main.py" That is the whole image. The agent's code runs as an unprivileged user in a filesystem that contains only the working directory. Everything else — the host's home directory, its sockets, its mounts — is simply not there. The interesting part is how the sandbox connects to the agent's loop. The agent needs to produce a command, and the harness needs to run that command in a fresh container, with resources capped and a deadline enforced. The container engine gives you the caps; Python gives you the deadline. python import subprocess import shlex def run in sandbox command: str, workdir: str, timeout: int = 60 - subprocess.CompletedProcess: docker = "docker", "run", "--rm", "--network", "none", "--memory", "512m", "--cpus", "1.0", "--read-only", "-v", f"{workdir}:/work:rw", "agent-sandbox:latest", "sh", "-c", command, return subprocess.run docker, capture output=True, text=True, timeout=timeout, Three flags carry most of the safety. --network none means the container cannot reach the network, so a dependency or a misbehaving model cannot exfiltrate or download. --read-only makes the container's own filesystem immutable, so nothing inside the image can be modified at runtime. --memory and --cpus cap the damage a runaway loop can do. The only writable location is the mounted working directory, which is where the agent's inputs and outputs live anyway. the harness side: fail the run, keep the host clean docker run --rm --network none --read-only \ -v "$ pwd /inbox:/inbox:ro" \ -v "$ pwd /outbox:/outbox:rw" \ agent-sandbox:latest python main.py Inputs go in read-only; outputs come out through a single writable mount. If the agent deletes everything in the container, the host notices nothing. Disposability is the property that turns a sandbox into a guarantee. A container you keep and reuse accumulates state: files left by a previous run, packages installed by a previous agent, environment drift that makes the next run behave differently. A container you create per run and destroy after the run has no history, and an agent with no history cannot be poisoned by one. The --rm flag deletes the container when the process exits. That covers the container itself. The working directory is another matter: it is mounted from the host, so anything the agent writes there survives. That is usually what you want — the outputs are the point of the run — but it means the working directory is the state boundary, and it should be created fresh per run and cleaned up when the run is done. Image tagging is part of the same discipline. latest moves; a pinned digest does not. If the sandbox image is rebuilt between runs, latest silently changes the environment the agent runs in, and a run that passed yesterday can fail today for reasons nobody can reproduce. Pin the image to a digest or a fixed tag so a sandbox run is reproducible. The sandbox is not free, and the costs show up in predictable places. Image availability is the first. A fresh container engine has no images cached, and pulling a base image takes time and bandwidth. The fix is to pull before the agent runs, not during it. The same applies to dependencies: if the container installs packages at startup, every run pays the network cost — and with --network none , it pays with failure. Bake dependencies into the image at build time. Mount performance is the second. Bind mounts are fine for code and small files, but a working directory with many files or heavy I/O can be dramatically slower inside a container than on the host, especially on macOS and Windows, where the mount crosses a virtualization boundary. For large artifacts, copy them into the container's own filesystem and copy results out, rather than streaming through the mount. Docker-in-Docker is the third. An agent that builds images inside its sandbox needs the Docker socket, and mounting the Docker socket into a container is the same as giving the container root on the host — it undoes the isolation you just bought. If the agent must build images, run a dedicated daemon inside the sandbox, or route through a remote builder with its own limits. Never mount the host socket. Containers are a boundary, not a safe. They share the kernel, so a kernel vulnerability can in principle cross the boundary, and containers do not stop resource exhaustion inside their own limits: an agent that is allowed 1 CPU can still peg that CPU. Secrets mounted into the container are secrets inside the container, and a read of a mounted secret file is a read the sandbox will permit. The sandbox shrinks the blast radius; it does not eliminate it. The discipline that makes sandboxes work is the same discipline that makes any security control work: the control is only as good as the policy around it. Fresh working directories per run, pinned images, no socket mounts, network off unless the task needs it, and a deadline on every run. None of these are hard, and together they turn an agent from a process that can touch everything into a process that can touch one directory, briefly. That is the trade worth making. The agent gets to be autonomous; the host gets to stay boring. Originally published on Dispatch https://dispatch-blog.hashnode.dev/give-your-ai-agent-a-sandbox-disposable-docker-isolation-in-practice .