Dev.to Security 🔐 Cybersecurity 👁 0 📖 7 min read

Sandboxing AI coding agents with Docker: a practical setup that limits the blast radius

AI coding agents are most useful when you stop approving every command. Let it run the tests, install the package, try the fix, run the tests again. That loop is where the time savings are. It is also the moment the agen

AI coding agents are most useful when you stop approving every command. Let it run the tests, install the package, try the fix, run the tests again. That loop is where the time savings are. It is also the moment the agent is running arbitrary shell commands with your user account, on the machine that holds your SSH keys, your cloud credentials and your browser cookies.

Most of the time nothing goes wrong. But "most of the time" is not a security model. An agent can misread an instruction and delete the wrong folder. It can read a prompt injection in a dependency's README and decide to upload your .env. It can install a typosquatted package because it misspelled a name.

The fix is not to watch harder. It is to run the agent somewhere a mistake can't reach anything important. This article builds that place with plain Docker: a container that can see one project folder, can't escalate privileges, and can only talk to the handful of domains it actually needs.

What the sandbox has to do

Before writing any config, it helps to be clear about the goal. A good agent sandbox:

  1. Sees only the project. No home directory, no SSH keys, no cloud credentials.
  2. Can't become root or gain extra Linux capabilities.
  3. Can only reach an allowlist of domains, like the model API and your package registry.
  4. Has resource limits, so a runaway loop can't freeze your machine.
  5. Is disposable. Every session starts clean and leaves nothing behind except changes in the project folder.

Everything below maps to one of those five.

Step 1: A small image for the agent

Start from an image that has your toolchain, then add the agent. This example uses Claude Code, but the same pattern works for any CLI agent.

FROM node:22-slim

RUN apt-get update \
 && apt-get install -y --no-install-recommends git ca-certificates python3 \
 && rm -rf /var/lib/apt/lists/*

RUN npm install -g @anthropic-ai/claude-code

USER node
WORKDIR /work

The official Node image already ships with an unprivileged node user, so the agent never runs as root inside the container. Add whatever your project needs to build and test: a Python version, Go, a database client. Keep it to what the agent actually uses.

docker build -t agent-sandbox .

Step 2: A network that can't reach the internet

Docker can create a network with no route to the outside world:

docker network create --internal agent-net

A container on agent-net can talk to other containers on that network and nothing else. That is the starting point: no internet at all.

Of course the agent needs some network. It has to call the model API, and probably install packages. So instead of opening everything, put a small proxy on the edge that only allows specific domains.

Step 3: An egress proxy with an allowlist

Squid is a mature, boring HTTP proxy, which is exactly what you want here. Create squid.conf:

http_port 3128

acl allowed dstdomain .anthropic.com
acl allowed dstdomain registry.npmjs.org
acl allowed dstdomain pypi.org files.pythonhosted.org
acl allowed dstdomain github.com codeload.github.com

http_access allow allowed
http_access deny all

Run it attached to both networks, the internal one and the normal bridge, so it is the only door out:

docker run -d --name egress \
  --network agent-net \
  -v "$PWD/squid.conf":/etc/squid/squid.conf:ro \
  ubuntu/squid

docker network connect bridge egress

The agent reaches the proxy by its container name, and the proxy decides what passes. A request to an allowed domain goes through. A request anywhere else, including the address a prompt injection told the agent to send your code to, gets a 403.

Adjust the list to your stack. Start strict and add domains when something legitimately fails. The failures will tell you exactly what your agent talks to, which is useful to know anyway.

Step 4: Run the agent locked down

Now put it together:

docker run --rm -it \
  --network agent-net \
  -e HTTPS_PROXY=http://egress:3128 \
  -e HTTP_PROXY=http://egress:3128 \
  -e ANTHROPIC_API_KEY \
  --cap-drop ALL \
  --security-opt no-new-privileges \
  --read-only \
  --tmpfs /tmp \
  --tmpfs /home/node \
  --memory 4g --cpus 2 --pids-limit 512 \
  -v "$PWD":/work \
  agent-sandbox claude

What each part buys you:

  • --network agent-net plus the proxy variables: the only way out is through the allowlist.
  • -e ANTHROPIC_API_KEY passes the key from your shell without writing it into the image or a file. Use a key created just for this, with a spending limit.
  • --cap-drop ALL and no-new-privileges: no extra kernel capabilities and no setuid escalation, even if something odd ends up in the image.
  • --read-only with two tmpfs mounts: the image itself can't be modified. Scratch space and the agent's own config live in memory and disappear when the container exits.
  • --memory, --cpus, --pids-limit: a fork bomb or an infinite build loop hits a ceiling instead of your laptop.
  • -v "$PWD":/work: the project folder is the only thing from your machine the agent can see or change.

Notice what is not there: no -v ~/.ssh, no -v ~/.aws, no -v ~/.gitconfig, and never -v /var/run/docker.sock. Mounting the Docker socket gives whoever is inside full control of Docker on the host, which defeats the whole exercise.

Step 5: Keep secrets out of the project folder

The agent can read everything in /work, so anything sensitive in the repo is fair game. Two habits handle most of it:

  • Keep real secrets out of the working tree. Load them from your secret manager or your shell at runtime, not from a committed or uncommitted .env.
  • If a local .env has to exist for your app, hide it from the container by mounting an empty file over it:
  -v /dev/null:/work/.env:ro \

Inside the container, .env exists but is empty. Your app on the host still sees the real one.

Step 6: Let Git be your undo button

The sandbox limits what the agent can reach. Git limits how much a bad session costs you.

  • Start each agent session on its own branch, or better, in its own worktree, so the agent's changes never mix with yours.
  • Don't give the container push access. No SSH keys and no Git credentials inside means the agent can commit but can't push. You review the diff on the host and push yourself.
  • If a session goes sideways, git restore . or delete the worktree. Nothing outside the project folder was touched.

Where this lets you relax

Inside this setup, you can reasonably let the agent run without approving every command. Claude Code even has a flag for skipping permission prompts, --dangerously-skip-permissions, and the name is a fair warning: it is only reasonable inside a sandbox like this one, never on your host. Anthropic also publishes a reference dev container for Claude Code that takes the same approach, with a firewall script that only allows specific domains. If you use VS Code dev containers, it is worth reading as a starting point.

The point is not that the agent is now trustworthy. It is that the worst case is now small: some bad changes in one folder that Git can undo, and a few blocked requests in the proxy log.

A short checklist

  • Agent runs in a container as a non root user
  • Only the project folder is mounted, never home, SSH, cloud config or the Docker socket
  • Internal network plus an egress proxy with a domain allowlist
  • All capabilities dropped and no-new-privileges set
  • Read only root filesystem with tmpfs for scratch space
  • Memory, CPU and process limits
  • A dedicated API key with a spending limit
  • Secrets kept out of the working tree, or masked with /dev/null
  • A separate branch or worktree per session, and no push credentials inside

Takeaway

You can't make an AI agent never make a mistake, but you can decide in advance how far a mistake can travel. One folder, one network door, no credentials and a Git history to roll back to. Set that up once, and letting the agent work on its own stops being a gamble.

Keep reading

Let's stay in touch

I'm Amit Shukla, a software engineer working across DevOps, cloud and applied AI. Behind the Stack is where I write up the things I learn the hard way, with working code and honest tradeoffs.

If this saved you some time, a reaction or a comment helps more people find it.

📰 Read the original article on Dev.to Security

Originally published by Dev.to Security. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.