n4nAI

Docker code execution setup for AutoGen with n4n.ai

Set up Docker-based code execution for AutoGen agents with secure sandboxing and n4n.ai routing for LLM calls.

n4n Team4 min read790 words

Audio narration

Coming soon — every post will get a voice note here.

AutoGen’s code execution capability lets agents write, run, and iterate on code autonomously. Running that code in Docker isolates it from your host, prevents dependency conflicts, and gives you a clean slate for every session. This guide walks through a production-ready Docker setup for AutoGen code execution, with n4n.ai handling the LLM routing behind a single OpenAI-compatible endpoint.

Step 1: Choose your Docker image strategy

You have two paths. The quickest is Microsoft’s official mcr.microsoft.com/autogen/autogen-agentchat image, which bundles Python, common data-science libraries, and the AutoGen runtime. The alternative — and the one I recommend for anything beyond a demo — is building your own image so you control the exact Python version, system packages, and pinned dependencies.

Create a Dockerfile in your project root:

# Dockerfile
FROM python:3.11-slim

# System deps for common data-science wheels
RUN apt-get update && apt-get install -y --no-install-recommends \
    build-essential \
    curl \
    git \
    && rm -rf /var/lib/apt/lists/*

# Create non-root user for safety
RUN useradd -m -u 1000 autogen && \
    mkdir -p /home/autogen/workspace && \
    chown -R autogen:autogen /home/autogen

WORKDIR /home/autogen/workspace
USER autogen

# Pin dependencies for reproducibility
COPY --chown=autogen:autogen requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# Default command keeps container alive for exec sessions
CMD ["sleep", "infinity"]

Your requirements.txt should pin everything:

# requirements.txt
autogen-agentchat==0.2.0
autogen-ext==0.2.0
pandas==2.2.2
numpy==1.26.4
matplotlib==3.8.4
requests==2.31.0

Build and tag it:

docker build -t autogen-sandbox:0.2.0 .

Step 2: Configure the Docker command-line executor

AutoGen’s DockerCommandLineCodeExecutor spins up a container per session, mounts a workspace directory, and tears it down when the agent finishes. Create a small wrapper module so your agent code stays clean:

# docker_executor.py
import os
import tempfile
from pathlib import Path
from autogen_ext.code_executors.docker import DockerCommandLineCodeExecutor

def create_executor(
    image: str = "autogen-sandbox:0.2.0",
    workspace: Path | None = None,
    timeout: int = 120,
) -> DockerCommandLineCodeExecutor:
    """
    Returns a configured Docker executor.
    Each call creates a fresh container; the workspace persists across
    executions within the same session.
    """
    if workspace is None:
        workspace = Path(tempfile.mkdtemp(prefix="autogen_ws_"))

    # Bind-mount the workspace so the container can read/write files
    bind_mounts = {str(workspace.resolve()): "/home/autogen/workspace"}

    return DockerCommandLineCodeExecutor(
        image=image,
        bind_mounts=bind_mounts,
        working_dir="/home/autogen/workspace",
        timeout=timeout,
        # Auto-remove container on exit; keeps host clean
        auto_remove=True,
    )

The bind_mounts mapping is the critical piece: the host directory becomes /home/autogen/workspace inside the container, matching the WORKDIR in your Dockerfile. The executor writes generated scripts there, runs them, and returns stdout/stderr to the agent.

Step 3: Wire the executor into an AutoGen agent

AutoGen 0.2+ uses the CodeExecutor protocol. Attach your executor to a UserProxyAgent (or any agent that implements code_executor):

# agent_setup.py
from autogen_agentchat.agents import AssistantAgent, UserProxyAgent
from autogen_agentchat.teams import RoundRobinGroupChat
from autogen_agentchat.messages import TextMessage
from autogen_ext.models.openai import OpenAIChatCompletionClient
from docker_executor import create_executor

# n4n.ai provides one OpenAI-compatible endpoint for 240+ models.
# Set OPENAI_BASE_URL to your n4n.ai gateway and OPENAI_API_KEY to your key.
model_client = OpenAIChatCompletionClient(
    model="gpt-4o-mini",  # routed through n4n.ai
    base_url=os.getenv("OPENAI_BASE_URL"),
    api_key=os.getenv("OPENAI_API_KEY"),
)

executor = create_executor()

coding_agent = AssistantAgent(
    name="coder",
    model_client=model_client,
    system_message=(
        "You write Python code to solve tasks. "
        "Use the workspace directory for any file I/O. "
        "Return only the code block, no markdown formatting."
    ),
)

user_proxy = UserProxyAgent(
    name="user_proxy",
    code_executor=executor,
    # Let the agent run up to 3 code blocks per turn
    max_consecutive_auto_reply=3,
)

team = RoundRobinGroupChat([coding_agent, user_proxy], max_turns=6)

Note the max_consecutive_auto_reply — without it, a single turn could spin into an unbounded loop of code execution. Three is a sensible default; tune it per task complexity.

Step 4: Run a verification task

Create a script that exercises the full loop: agent writes code, Docker executes it, result flows back, agent decides next step.

# verify.py
import asyncio
import os
from agent_setup import team

async def main():
    task = (
        "Create a file called 'fibonacci.py' that defines a function "
        "fib(n) returning the nth Fibonacci number. Then write a second "
        "script 'test_fib.py' that imports it and prints fib(10). "
        "Run the test script and show me the output."
    )

    async for msg in team.run_stream(task=task):
        if isinstance(msg, TextMessage):
            print(f"[{msg.source}] {msg.content}")

if __name__ == "__main__":
    asyncio.run(main())

Run it:

export OPENAI_BASE_URL="https://api.n4n.ai/v1"
export OPENAI_API_KEY="your-n4n-key"
python verify.py

You should see the agent write two files, execute test_fib.py, and print 55. The workspace directory (printed in logs if you add debug logging) will contain both scripts — useful for post-mortem debugging.

Step 5: Harden the sandbox for production

The default setup is functional but permissive. Apply these guards before exposing the endpoint to untrusted input.

Limit container resources

Add resource constraints to the executor creation:

# docker_executor.py (updated create_executor)
return DockerCommandLineCodeExecutor(
    image=image,
    bind_mounts=bind_mounts,
    working_dir="/home/autogen/workspace",
    timeout=timeout,
    auto_remove=True,
    # Resource limits — adjust to your workload
    container_kwargs={
        "mem_limit": "512m",
        "cpu_count": 1,
        "pids_limit": 64,
        "network_mode": "none",  # no outbound network
    },
)

network_mode: "none" prevents the container from reaching the internet or your internal network. If your tasks need to fetch data, create a sidecar proxy or pre-bake datasets into the image.

Drop capabilities and enforce read-only root filesystem

Extend container_kwargs:

container_kwargs={
    "mem_limit": "512m",
    "cpu_count": 1,
    "pids_limit": 64,
    "network_mode": "none",
    "cap_drop": ["ALL"],
    "security_opt": ["no-new-privileges:true"],
    "read_only": True,
    "tmpfs": {
        "/tmp": "size=100m,noexec,nosuid,nodev",
        "/home/autogen/workspace": "size=200m,exec",
    },
},

The read_only root filesystem forces all writes into the tmpfs mounts. The workspace gets exec so Python can run scripts there; /tmp is noexec to block binary execution from world-writable directories.

Use a dedicated Docker network (optional)

If you run multiple executor instances concurrently, put them on an isolated bridge network with no gateway:

docker network create --driver bridge --internal autogen-sandbox

Then add network="autogen-sandbox" to container_kwargs. Containers can talk to each other if you need multi-container tasks, but nothing reaches the host or internet.

Step 6: Handle streaming and long-running executions

The default timeout=120 covers most data-science tasks. For streaming results (e.g., a training loop that logs progress), increase the timeout and set up a callback:

# agent_setup.py (add to user_proxy)
from autogen_ext.code_executors.docker import DockerCommandLineCodeExecutor

def on_code_execution_start(executor: DockerCommandLineCodeExecutor, code: str):
    print(f"[exec] Running {len(code)} bytes of code...")

def on_code_execution_end(executor: DockerCommandLineCodeExecutor, result):
    print(f"[exec] Exit code: {result.exit_code}, stdout: {result.output[:200]}...")

executor = create_executor(timeout=600)
executor.on_code_execution_start = on_code_execution_start
executor.on_code_execution_end = on_code_execution_end

These hooks let you surface progress to a UI or log aggregation system without parsing agent messages.

Step 7: Persist workspace across sessions (when you need it)

By default, create_executor creates a fresh temp directory each call. For multi-turn conversations where the agent builds on prior artifacts, reuse the same workspace:

# session_manager.py
from pathlib import Path
from docker_executor import create_executor

class SessionManager:
    def __init__(self, base_dir: Path = Path("./sessions")):
        self.base_dir = base_dir
        self.base_dir.mkdir(parents=True, exist_ok=True)

    def get_executor(self, session_id: str):
        ws = self.base_dir / session_id
        ws.mkdir(exist_ok=True)
        return create_executor(workspace=ws)

Pass the same session_id for all turns in a conversation. Clean up old sessions with a cron job or TTL policy.

Step 8: Verify the hardened setup

Run the verification script again. Confirm:

  1. Container starts and exits cleanly (check docker ps -a — nothing should linger).
  2. No outbound network calls succeed (try requests.get("http://example.com") in the agent task — it should fail).
  3. Memory/CPU limits trigger on runaway tasks (write a while True: pass loop and watch the container OOM kill).
  4. Workspace files persist only where expected.

Add a quick smoke test to CI:

# test_smoke.py
import pytest
from agent_setup import team

@pytest.mark.asyncio
async def test_fibonacci_task():
    result = await team.run(
        task="Write and run a script that prints the 10th Fibonacci number."
    )
    assert "55" in str(result)

Run with pytest test_smoke.py -v. This catches image regressions, executor misconfiguration, and routing failures early.

Troubleshooting common failures

Symptom Likely cause Fix
docker: Error response from daemon: pull access denied Image not built or wrong tag Run docker build -t autogen-sandbox:0.2.0 . and verify with docker images
Permission denied on workspace writes UID/GID mismatch Ensure Dockerfile useradd -u 1000 matches host user, or use docker run --user $(id -u):$(id -g)
TimeoutError after 120s Task exceeds default timeout Increase timeout in create_executor or optimize the agent’s code
ModuleNotFoundError inside container Missing package in requirements.txt Rebuild image with the missing dependency pinned
Agent loops indefinitely max_consecutive_auto_reply too high or missing Set a low value (3-5) and add a termination condition in the system message

What this gives you

A reproducible, isolated code execution environment that:

  • Spins up in ~2 seconds on a warm host (image cached)
  • Enforces memory, CPU, and network boundaries
  • Persists artifacts only where you decide
  • Routes LLM calls through a single endpoint that handles fallback and metering automatically

The same pattern scales to Kubernetes — swap DockerCommandLineCodeExecutor for a custom executor that pods kubectl run with identical resource limits. The agent logic stays untouched.

Tagsautogendockercode-executionn4n-ai

Written by

n4n Team

The team building n4n — a single OpenAI-compatible API in front of 240+ models, with automatic fallback, load balancing and pay-per-token metering.

More from n4n Team →

All autogen getting started with n4n.ai posts →