- Published on
Running Claude Code in Docker/Podman Containers: A Practical Guide
- Authors

- Name
- Yusheng Zheng (云微)
- @yunwei37
Running Claude Code in Docker/Podman Containers: A Practical Guide
AI coding agents like Claude Code are increasingly being deployed in cloud environments, but how do you actually run them inside containers with proper resource limits? In our recent paper AgentCgroup: Understanding and Controlling OS Resources of AI Agents, we analyzed 144 software engineering tasks and found that OS-level execution consumes 56-74% of total task completion time, memory spikes can reach up to 15.4x peak-to-average ratios during tool calls, and resource requirements are highly unpredictable across tasks and models. These findings motivated us to build AgentCgroup, an eBPF-based resource management system for AI agents.
But before any of that research could happen, we needed a reliable way to run Claude Code inside containers. Getting this right turned out to be surprisingly tricky — there are subtle issues around user namespaces, SSL certificates, library dependencies, and permission models that can trip you up. This post shares the working configuration we arrived at after extensive trial and error, so you can skip straight to a setup that works.
Why Run Claude Code in a Container?
Whether you're doing research like ours or just want to sandbox your AI agent, running Claude Code in a container gives you:
- Resource control — Set memory and CPU limits to prevent runaway agents from consuming your entire machine
- Reproducibility — Test against standardized environments like SWE-bench Docker images
- Observability — Collect trace logs and monitor resource usage with cgroup metrics
- Isolation — Keep agent activities contained without affecting the host system
Working Configuration
After extensive testing, the following configuration works:
podman run --rm \
--userns=keep-id \
--network=host \
-v /usr:/usr:ro \
-v /lib:/lib:ro \
-v /lib64:/lib64:ro \
-v /etc:/etc:ro \
-v /bin:/bin:ro \
-v /sbin:/sbin:ro \
-v /home:/home \
-v /tmp:/tmp \
-v /var:/var \
-w /tmp \
-e HOME=/home/$(whoami) \
-e PATH=/usr/local/bin:/usr/bin:/bin \
--memory=4g --cpus=2 \
docker.io/library/debian:bookworm-slim \
claude --model haiku --print --dangerously-skip-permissions "Your prompt here"
Key Parameters Explained
| Parameter | Purpose |
|---|---|
--userns=keep-id | Preserves host user ID, required for writing to ~/.claude |
--network=host | Uses host networking for API calls |
-v /usr:/usr:ro | Shares Node.js, Claude CLI, and system binaries |
-v /lib:/lib:ro | Shares system libraries (glibc, libnode, etc.) |
-v /lib64:/lib64:ro | Shares 64-bit libraries |
-v /etc:/etc:ro | Shares SSL certificates and system config |
-v /home:/home | Shares home directory for ~/.claude config and traces |
-v /tmp:/tmp | Shares temp directory |
--memory=4g | Memory limit for resource control experiments |
--cpus=2 | CPU limit for resource control experiments |
Trace Log Location
Claude Code writes trace logs to ~/.claude/projects/<workdir>/.
For example, if working directory is /tmp, traces go to:
~/.claude/projects/-tmp/<session-id>.jsonl
Tested Scenarios
Basic Test (Working)
# Simple prompt
podman run --rm \
--userns=keep-id \
--network=host \
-v /usr:/usr:ro -v /lib:/lib:ro -v /lib64:/lib64:ro \
-v /etc:/etc:ro -v /bin:/bin:ro -v /sbin:/sbin:ro \
-v /home:/home -v /tmp:/tmp -v /var:/var \
-w /tmp \
-e HOME=/home/yunwei37 \
-e PATH=/usr/local/bin:/usr/bin:/bin \
--memory=4g --cpus=2 \
docker.io/library/debian:bookworm-slim \
claude --model haiku --print --dangerously-skip-permissions "What is 2+2?"
# Output: 4
Code Generation (Working)
# Generate code
podman run --rm \
--userns=keep-id \
--network=host \
-v /usr:/usr:ro -v /lib:/lib:ro -v /lib64:/lib64:ro \
-v /etc:/etc:ro -v /bin:/bin:ro -v /sbin:/sbin:ro \
-v /home:/home -v /tmp:/tmp -v /var:/var \
-w /tmp \
-e HOME=/home/yunwei37 \
-e PATH=/usr/local/bin:/usr/bin:/bin \
--memory=4g --cpus=2 \
docker.io/library/debian:bookworm-slim \
claude --model haiku --print --dangerously-skip-permissions \
"Write a Python function that checks if a number is prime."
# Claude creates /tmp/prime.py with the implementation
SWE-bench Container (Working)
SWE-bench provides standardized Docker images for evaluating code repair agents. Each image contains a specific GitHub issue's codebase at the exact commit before the fix.
Image naming format: swerebench/sweb.eval.x86_64.<repo>_<id>_<repo>-<issue>
Example: Fixing starlette issue #1147 (session cookie path)
# Pull the SWE-bench image
podman pull docker.io/swerebench/sweb.eval.x86_64.encode_1776_starlette-1147
# Run Claude haiku to fix the issue
podman run --rm \
--userns=keep-id \
--network=host \
-v /usr:/usr:ro -v /lib:/lib:ro -v /lib64:/lib64:ro \
-v /etc:/etc:ro -v /bin:/bin:ro -v /sbin:/sbin:ro \
-v /home:/home -v /tmp:/tmp -v /var:/var \
-w /testbed \
-e HOME=/home/yunwei37 \
-e PATH=/usr/local/bin:/usr/bin:/bin \
--memory=4g --cpus=2 \
docker.io/swerebench/sweb.eval.x86_64.encode_1776_starlette-1147 \
bash -c '
git config --global user.email "[email protected]"
git config --global user.name "Test"
git config --global --add safe.directory /testbed
claude --model haiku --print --dangerously-skip-permissions \
"Fix this issue: $(cat /issue.md)"
git diff
'
Note: First run with --userns=keep-id may take longer as Podman creates ID-mapped layer copies. Subsequent runs are faster.
Important: The /testbed directory in SWE-bench images is owned by root. You need a two-step process:
Step 1: Create a modified image with fixed permissions
# Create temp container, fix permissions, commit as new image
TEMP=$(podman run -d docker.io/swerebench/sweb.eval.x86_64.encode_1776_starlette-1147 sleep 60)
podman exec $TEMP chown -R 1000:1000 /testbed
podman commit $TEMP swebench-starlette-1147-fixed
podman stop $TEMP && podman rm $TEMP
Step 2: Run Claude with userns=keep-id
podman run --rm \
--userns=keep-id \
--network=host \
-v /usr:/usr:ro -v /lib:/lib:ro -v /lib64:/lib64:ro \
-v /etc:/etc:ro -v /bin:/bin:ro -v /sbin:/sbin:ro \
-v /home:/home -v /tmp:/tmp -v /var:/var \
-w /testbed \
-e HOME=/home/$(whoami) \
-e PATH=/usr/local/bin:/usr/bin:/bin \
--memory=4g --cpus=2 \
swebench-starlette-1147-fixed \
bash -c '
git config user.email "[email protected]"
git config user.name "Test"
git config --add safe.directory /testbed
claude --model haiku --print --dangerously-skip-permissions \
"Fix this issue: $(cat /issue.md)"
git diff
'
Example output (starlette-1147 fix):
diff --git a/starlette/middleware/sessions.py b/starlette/middleware/sessions.py
- header_value = "%s=%s; path=/; Max-Age=%d; %s" % (
+ path = scope.get("root_path", "") or "/"
+ header_value = "%s=%s; path=%s; Max-Age=%d; %s" % (
self.session_cookie,
data.decode("utf-8"),
+ path,
self.max_age,
The trace logs will be saved to ~/.claude/projects/-testbed/.
Available SWE-rebench images (with Docker support):
- Data Processing: dask (72), geopandas (20)
- ML/Scientific: pennylane (76), haystack (22)
- DevOps/Build: conan (73), dvc (72), briefcase (40)
- Web/Network: starlette (27), httpx (25)
See: https://huggingface.co/datasets/nebius/SWE-rebench
Troubleshooting
Problem: Permission denied writing to ~/.claude
Solution: Use --userns=keep-id to preserve user ID mapping.
Problem: SSL certificate errors / HTTPS failures
Solution: Mount /etc:/etc:ro to share SSL certificates.
Problem: libnode.so.115 not found
Solution: Mount both /lib:/lib:ro and /lib64:/lib64:ro.
Problem: Claude hangs with no output
Possible causes:
- Missing SSL certificates (mount /etc)
- User ID mismatch (use --userns=keep-id)
- stdin issues (ensure not running interactively without -it)
Problem: "Cannot run with root privileges"
Solution: Use --userns=keep-id instead of running as root or using --user.
Failed Approaches
The following approaches did NOT work:
Mounting only specific libraries: Too many dependencies, hard to track all of them.
Using
--user $(id -u):$(id -g)without--userns=keep-id: User ID mapping issues cause permission errors.Not mounting /etc: SSL certificate verification fails, causing API calls to hang.
Mounting ~/.claude to a different path: Claude expects ~/.claude relative to $HOME.
Resource Monitoring
To monitor resource usage during experiments:
# In another terminal, watch container stats
podman stats --no-stream <container-id>
# Or use cgroup metrics directly
cat /sys/fs/cgroup/user.slice/user-1000.slice/memory.current
Next Steps
- Test with more complex SWE-bench tasks
- Integrate with trace collection scripts
- Add cgroup monitoring hooks