On this page#
- What the integration actually does
- The wiring
- The two things that break it
- Verifying it works
- Team considerations
- FAQ
What the integration actually does#
Running Claude Code in Docker creates a controlled execution boundary around the agent; it does not create a security boundary around your entire development process. The container limits which files, tools, credentials, and host capabilities Claude Code can reach, while your repository, Git history, and review process remain the real controls.
Claude Code is an agentic coding tool that reads repositories, edits files, and runs commands. Docker packages that toolchain into a reproducible environment. The useful integration is therefore not “Claude Code installed in a container.” It is a deliberately narrow contract:
- Mount only the repository Claude Code needs.
- Run as a non-root user.
- expose credentials explicitly, never by mounting your entire home directory.
- Persist agent configuration intentionally.
- Keep Docker’s control socket outside the container unless Docker control is the actual requirement.
- Treat generated changes as untrusted until the repository’s normal checks pass.
| Common assumption | What actually happens | |---|---| | The container protects the repository | A writable bind mount lets the agent modify or delete mounted files | | The environment is reproducible | Only declared packages, mounts, environment variables, and entrypoints are reproducible | | Root inside the container is harmless | Root-created files can become undeletable or awkward on the host | | Claude Code can use host tools | It can use only binaries and services deliberately exposed to it | | A successful prompt proves integration | It proves almost nothing about permissions, persistence, or test execution |
The Anthropic overview explains the agent’s capabilities. The missing operational point is that Docker does not decide your trust model. Your mounts and runtime permissions do.
Photo by cottonbro studio on Pexels.
The wiring#
Use a repository-owned Dockerfile, Compose configuration, and Claude Code settings file. That makes the integration reviewable with the code it can change. A shell alias assembled on one developer’s laptop is not team infrastructure; it is an undocumented exception waiting to fail.
1. Build a small, non-root agent image#
Start from the same runtime family your repository already expects, then install Claude Code and only the tools required by repository checks. Do not copy the repository into the image: mount it at runtime so edits remain visible to the host and image rebuilds do not capture source or secrets.
FROM node
RUN npm install --global @anthropic-ai/claude-code
RUN useradd --create-home --shell /bin/bash agent
WORKDIR /workspace
USER agent
CMD ["claude"]This intentionally avoids installing a broad “developer toolbox.” Add Git, language runtimes, package managers, or system libraries only when a repository command requires them. Docker’s official documentation covers the underlying image, user, mount, and runtime concepts; keep those mechanics aligned with Docker’s documented behavior.
Do not place an API key in the Dockerfile, build arguments, image labels, or committed environment files. Build artifacts are the wrong secret boundary.
2. Declare the runtime contract in Compose#
Compose should describe the smallest useful connection between the agent and the repository. Mount the current repository at /workspace, pass authentication from the caller’s environment, prevent privilege escalation, and give the process an init implementation so interrupted commands are reaped correctly.
services:
claude:
build:
context: .
dockerfile: Dockerfile.claude
working_dir: /workspace
init: true
stdin_open: true
tty: true
environment:
ANTHROPIC_API_KEY: ${ANTHROPIC_API_KEY}
volumes:
- .:/workspace
- claude-home:/home/agent
security_opt:
- no-new-privileges:true
volumes:
claude-home:The named home volume preserves agent state without exposing ~/.ssh, cloud credentials, browser sessions, or unrelated repositories from the host. Mounting the host home directory is a bad shortcut: it turns a narrow repository integration into broad credential access.
If the repository does not need outbound network access during agent execution, disable it:
services:
claude:
network_mode: noneThat mode will also block dependency downloads and remote model access, so it is only usable when those dependencies are provided through another approved path. Do not copy security-looking settings that make the tool nonfunctional.
3. Put agent permissions under repository review#
Claude Code settings should express the commands the repository expects, not grant unrestricted shell access. A permission allowlist is a list of approved operations; it reduces accidental reach and makes capability changes visible in code review.
{
"permissions": {
"allow": [
"Bash(npm test)",
"Bash(npm run lint)",
"Bash(npm run typecheck)",
"Bash(git status)",
"Bash(git diff *)"
],
"deny": [
"Bash(git push *)",
"Bash(git clean *)",
"Bash(docker *)"
]
}
}Store this as .claude/settings.json when the policy belongs to the repository. Confirm the exact supported settings syntax against the Claude Code documentation; a configuration that Claude Code ignores is not a control.
Broad patterns such as Bash(*) defeat the point. If your approved test script can execute arbitrary user-controlled arguments, the apparent allowlist is also broader than it looks.
For a fuller repository operating contract—commands, invariants, handoff rules, and review gates—the PairFoundry Agent Operating Kit is the relevant next layer. Docker supplies isolation mechanics; it does not supply operating discipline.
4. Make invocation boring#
Expose one checked-in command that builds and starts the service. The command should fail immediately when required authentication is absent and should not silently substitute credentials from another location.
test -n "$ANTHROPIC_API_KEY" || {
echo "ANTHROPIC_API_KEY is required" >&2
exit 1
}
docker compose run --rm claudeKeep the script name and required prerequisites in the repository README. The broader Wiring It In collection covers this boundary between an agent capability and a maintainable repository workflow.
The two things that break it#
Two failures dominate this setup: host files become owned by the container’s user identity, or the bind mount hides files created during the image build. Both failures come directly from Docker’s filesystem model, and neither is fixed by repeatedly rebuilding the image.
Permission denied and root-owned repository files#
If Claude Code can read the repository but cannot edit it—or edits produce files the host user cannot remove—the container and host user IDs do not match. Running the container as root appears to fix the first error, but creates the second. That is not a fix.
Typical symptoms include:
Permission denied
EACCES: permission deniedOn a host where numeric identity matters, pass the caller’s user and group IDs through Compose:
services:
claude:
user: "${LOCAL_UID}:${LOCAL_GID}"Invoke it with those values set by your repository launcher. Ensure the mounted home directory remains writable for that identity. Do not solve the problem with recursive world-writable permissions; that expands access while concealing the ownership mismatch described by Docker’s container and storage model.
Dependencies “disappear” when the container starts#
If the image build installs dependencies under /workspace and runtime Compose mounts . onto /workspace, the bind mount covers the built files. The image still contains them, but the running container sees the host directory at that path instead. Rebuilding cannot change this precedence.
Typical symptoms include:
command not found
Cannot find module
No such file or directoryChoose one ownership model:
services:
claude:
volumes:
- .:/workspace
- workspace_modules:/workspace/node_modules
volumes:
workspace_modules:Alternatively, install dependencies after mounting the repository and accept that the host tree owns them. Do not mix both models without documenting which location is authoritative.
Photo by Daniil Komov on Pexels.
Verifying it works#
Verification must prove identity, mount boundaries, persistence, command availability, and repository checks. A Claude Code prompt returning an answer proves only that the process started and reached the model; it does not prove that the integration is safe or reproducible.
Run these checks before asking the agent to change code:
id
pwd
git status --short
test -w /workspace
command -v claude
command -v npmThen verify the boundaries:
- Create a disposable file inside
/workspace; confirm it appears on the host with usable ownership. - Remove it from the host; confirm the container sees the removal.
- Restart the container; confirm intended Claude state persists in the named home volume.
- Confirm unrelated host directories and credentials are absent.
- Run linting, type checking, and tests through the exact commands allowed to the agent.
- Attempt a denied command and confirm Claude Code blocks it rather than merely warning.
Finish with git diff and the repository’s normal review path. Developers who need the underlying agent workflow before adopting packaged controls can start with the free PairFoundry Foundations track.
Team considerations#
The second developer will expose every hidden assumption in the setup: host identity, CPU architecture, credential source, shell behavior, cached volumes, and undocumented dependencies. A team-ready integration therefore defines prerequisites, reset procedures, permission ownership, and one canonical launch path inside the repository.
Commit these files:
Dockerfile.claudecompose.yaml.claude/settings.json- The launcher script
- A short operating section in the README
Do not commit secrets, personal Claude state, machine-specific paths, or a copied home directory. Document how to recreate named volumes and how to recover when cached dependencies become stale. A clean clone should be the acceptance test.
Permission changes deserve code review because they change what the agent can do. Dependency additions deserve the same review because each binary expands available behavior. If different repositories need different controls, keep their policies local instead of creating one powerful global agent container.
For teams comparing packaged workflows rather than assembling everything manually, the PairFoundry packs overview shows the available operating layers.
Photo by panumas nikhomkhai on Pexels.
Related reading#
- Claude Code JetBrains: what to check before you trust the connection
- Claude Code Xcode — a setup that survives a second developer
FAQ#
Should Claude Code get access to the Docker socket?#
Usually no. Mounting the Docker socket gives the container control over the host Docker daemon, which can collapse the isolation you intended to create. Expose it only when container management is an explicit repository requirement, then treat that agent as host-privileged.
How is this different from following the official setup?#
Official documentation explains supported installation and product behavior. This setup adds repository-specific boundaries: non-root execution, narrow mounts, reviewed command permissions, persistent state, ownership handling, and verification. Those are engineering-policy decisions, so no vendor can choose them for your repository.
What is the safest way to roll back a broken setup?#
Stop the service, inspect git diff, revert only the unwanted repository changes through your normal version-control process, and recreate the named volumes if cached state caused the failure. Do not delete volumes until you have confirmed they contain no state you need.
When should a team avoid this approach?#
Avoid it when the agent requires routine access to many host credentials, desktop applications, hardware devices, or privileged Docker control. At that point the container becomes a complicated wrapper with porous boundaries. Use a dedicated development environment or a more explicit remote execution model instead.
What must a teammate configure locally?#
They need Docker, the repository, an approved authentication method, and any host identity variables required by the mount strategy. They should not need personal path edits or undocumented shell aliases. If they do, the integration is not finished.