Problem: AI Agents Running Loose on Your Host
Your AI agent works locally, but deploying it means giving it shell access, file system reach, and network freedom on your production machine. One bad tool call — a hallucinated rm -rf, a runaway loop, an unintended API blast — and you're dealing with the fallout directly.
You'll learn:
- How to containerize an AI agent with proper isolation
- How to cap CPU, memory, and network access per container
- How to handle secrets and tool output safely
Time: 25 min | Level: Intermediate
Why This Happens
AI agents that use tools (file I/O, shell commands, web requests) inherit the permissions of whatever process runs them. On a dev machine that's annoying. In production it's a liability.
Docker gives each agent its own filesystem, process namespace, and network stack. The agent can do whatever it needs inside the container, and the blast radius stays contained.
Common symptoms that tell you isolation is overdue:
- Agent tool calls modifying files outside the project directory
- Agents consuming unbounded memory during long tasks
- No audit trail for what an agent actually executed
Solution
Step 1: Write a Minimal Agent Dockerfile
Start lean. You want a small attack surface and fast rebuilds.
# Use slim variant — avoids 400MB of unnecessary tooling
FROM python:3.12-slim
# Create a non-root user — agents should never run as root
RUN useradd --create-home --shell /bin/bash agent
WORKDIR /home/agent/app
# Install deps as root, then drop privileges
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Copy only what the agent needs
COPY src/ ./src/
# Switch to non-root before running
USER agent
CMD ["python", "src/agent.py"]
Why non-root matters: even with Docker's namespace isolation, a root process inside a container can escape certain kernel vulnerabilities. Running as agent limits the damage ceiling.
Expected: docker build -t my-agent:latest . completes in under 60 seconds on a warm cache.
If it fails:
pippermission error: Make sureCOPY requirements.txthappens beforeUSER agentsrc/not found: Confirm your build context is the project root
Step 2: Set Resource Limits at Runtime
Don't let a runaway agent take down the host. Apply hard caps when you docker run.
docker run \
--name agent-worker \
--memory="512m" \ # Hard RAM cap — OOM kills the container, not the host
--cpus="1.0" \ # Limit to 1 CPU core
--pids-limit=100 \ # Prevent fork bombs
--read-only \ # Immutable filesystem by default
--tmpfs /tmp:size=100m \ # Agents often need /tmp — give them a capped one
--network=agent-net \ # Isolated network (created in Step 3)
-e OPENAI_API_KEY="$OPENAI_API_KEY" \
my-agent:latest
Why --read-only with --tmpfs: the agent can write scratch files to /tmp without touching the container's real filesystem layer. When the container stops, /tmp disappears.
Expected: docker stats agent-worker shows memory capped at ~512MiB.
If it fails:
- Agent crashes on startup: It may be writing to a path outside
/tmp. Check logs withdocker logs agent-workerand add more--tmpfsmounts as needed
Step 3: Create an Isolated Network
By default, Docker containers share a bridge network and can reach each other freely. Give agents their own network with no internet access unless they explicitly need it.
# Internal-only network — no outbound internet
docker network create \
--driver bridge \
--internal \
agent-net
# If your agent needs to call external APIs, use this instead:
docker network create \
--driver bridge \
agent-net-egress
For agents that need selective outbound access, pair the internal network with an egress proxy container (Squid, Envoy) and only whitelist the domains your agent actually calls.
Step 4: Pass Secrets Safely
Never bake API keys into the image. Two safe options:
Option A — Environment variables at runtime (simplest):
# Read from your shell environment, never hardcode
docker run --env OPENAI_API_KEY="$OPENAI_API_KEY" my-agent:latest
Option B — Docker secrets (production-grade):
# Create the secret once
echo "sk-..." | docker secret create openai_key -
# Reference it in a Compose file
services:
agent:
image: my-agent:latest
secrets:
- openai_key
secrets:
openai_key:
external: true
Inside the container, the secret appears at /run/secrets/openai_key — read it in Python with open('/run/secrets/openai_key').read().strip().
Step 5: Compose It for Multi-Agent Setups
When you're running several agents (planner, executor, critic), use Docker Compose to manage the whole stack.
# compose.yml
services:
planner:
build: .
image: my-agent:latest
networks:
- agent-internal
environment:
- AGENT_ROLE=planner
deploy:
resources:
limits:
cpus: "0.5"
memory: 256M
executor:
image: my-agent:latest
networks:
- agent-internal
- egress # Only executor gets outbound access
environment:
- AGENT_ROLE=executor
deploy:
resources:
limits:
cpus: "1.0"
memory: 512M
read_only: true
tmpfs:
- /tmp:size=100m
networks:
agent-internal:
internal: true # No internet
egress:
driver: bridge # Internet allowed
docker compose up --build
Expected: Both containers start, only executor can reach external APIs.
Verification
# Check containers are running with correct limits
docker stats --no-stream
# Confirm the agent process is NOT running as root
docker exec agent-worker whoami # Should return: agent
# Test network isolation — this should fail inside agent-net
docker run --rm --network=agent-net alpine ping -c 1 8.8.8.8
You should see: whoami returns agent, the ping times out or is refused.
What You Learned
- Run agents as non-root inside containers to limit kernel-level escape risk
- Use
--memory,--cpus, and--pids-limitto prevent resource exhaustion - Separate agent networks block lateral movement between containers
--read-onlywith--tmpfs /tmpgives scratch space without persistent writes- Docker secrets are safer than env vars for long-lived production deployments
Limitation: Docker isolation isn't a sandbox for arbitrary untrusted code. If your agent executes user-supplied shell commands, add a second layer like gVisor (--runtime=runsc) or Firecracker microVMs.
When NOT to use this pattern: for single-shot, short-lived agent calls in a trusted environment, the overhead of container spin-up (1–3 seconds) may not be worth it. Use containers for persistent agents, scheduled workers, or anything with tool access.
Tested on Docker 27.x, Docker Compose v2.x, Python 3.12, Ubuntu 24.04 & macOS 15