Run Claude Code inside a Podman container, sandboxed to the directory you launch it from.
Claude runs with --dangerously-skip-permissions, so it never stops to ask — but it can
only see the current project, your ~/.claude config and nothing else of your machine.
cd ~/dev/some-project
pclaude1. Install Podman
sudo dnf install podman # Fedora / RHEL / CentOS
sudo apt install podman # Debian / Ubuntu
sudo pacman -S podman # Arch
brew install podman && podman machine init && podman machine start # macOS2. Install pclaude
git clone https://github.com/brickpop/pclaude.git
cd pclaude
./install.shThis copies pclaude to ~/.local/bin, the Dockerfile to ~/.local/share/pclaude,
and builds the image (a few minutes).
If ~/.local/bin is not on your PATH:
fish_add_path ~/.local/bin # fish
echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc # bash/zsh3. Log in once, on the host, with plain claude /login. That single login covers
pclaude too, and survives image rebuilds.
Claude Code reads ~/.claude/.credentials.json on Linux, but on macOS it keeps the token
in the login Keychain (item Claude Code-credentials), which the Linux container cannot
reach. So on macOS pclaude keeps the two in sync, before launching and again when the
container exits: whichever copy expires later wins and overwrites the other. The file is
written with mode 0600; the Keychain item is updated in place with security.
Syncing both ways matters because every token refresh revokes the previous refresh token.
If the container refreshes and the Keychain keeps the old copy, both host claude and the
next pclaude would be left with a dead login.
To log out everywhere, run claude /logout on the host and delete
~/.claude/.credentials.json; otherwise the next pclaude restores the login into the
Keychain from the file.
If you authenticate with ANTHROPIC_API_KEY, Bedrock or Vertex instead, no login is
needed at all — those are forwarded from the environment.
Arguments are passed through to claude untouched:
pclaude # interactive session
pclaude -p "explain this repo" # print mode
pclaude --model opus --resume # any claude flag
pclaude mcp list # any claude subcommandThe launcher's own options are namespaced so they can never clash with a Claude flag:
| Option | |
|---|---|
--pclaude-build |
Build the image |
--pclaude-update |
Rebuild it from scratch, pulling the latest base and Claude Code |
--pclaude-shell |
Open a shell in the container instead of Claude |
--pclaude-help |
Usage |
The image sets claude as its CMD and clears the entrypoint, so Claude is only the
default — any command in the image can replace it, with no --entrypoint needed:
podman run --rm -it -v $PWD:$PWD:z -w $PWD localhost/pclaude # claude
podman run --rm -it -v $PWD:$PWD:z -w $PWD localhost/pclaude bash # a shell
podman run --rm -v $PWD:$PWD:z -w $PWD localhost/pclaude forge test # anything elsepclaude --pclaude-shell is just the shorthand for the second one, with all the usual
mounts applied.
| Host | Container | |
|---|---|---|
$PWD |
$PWD (same path) |
read-write — the only project code Claude can reach |
~/.claude |
/home/node/.claude |
credentials, settings, agents, history |
~/.claude.json |
/home/node/.claude.json |
project state |
~/.gitconfig |
/home/node/.gitconfig |
read-only, if present |
ANTHROPIC_API_KEY, ANTHROPIC_BASE_URL, GH_TOKEN and friends are forwarded when set.
Nothing else from your machine is visible. Running in $HOME itself is refused (it would
defeat the point); set PCLAUDE_ALLOW_HOME=1 if you really mean it.
The project keeps its host path inside the container, and HOME stays /home/node.
Claude keys per-project session history off the working directory, so each repo gets its
own history and pclaude --continue picks up where claude --continue left off in that
same repo.
Note that the container has unrestricted network access — the sandbox is about your filesystem, not about what Claude can reach online.
On Fedora/RHEL, bind mounts are relabelled with :z (shared) so that repeated and
concurrent sessions work. The stricter :Z gives each container a private MCS category,
which means a second pclaude session would relabel ~/.claude out from under the first
and break it — use PCLAUDE_RELABEL=Z only if you run one session at a time. On
non-SELinux hosts no suffix is added at all.
To undo the relabelling of a directory later: restorecon -R <dir>.
| Variable | Default | |
|---|---|---|
PCLAUDE_IMAGE |
localhost/pclaude |
image to run |
PCLAUDE_HOME |
~/.local/share/pclaude |
where the Dockerfile lives |
PCLAUDE_ARGS |
— | extra podman run args |
PCLAUDE_NO_YOLO |
— | 1 restores Claude's permission prompts |
PCLAUDE_RELABEL |
auto |
z, Z or off |
PCLAUDE_ALLOW_HOME |
— | 1 allows running in $HOME |
Mount something extra:
PCLAUDE_ARGS="-v $HOME/dev/shared-lib:/shared:z" pclaudeForward your SSH agent, so Claude can push:
PCLAUDE_ARGS="-v $SSH_AUTH_SOCK:/ssh-agent:z -e SSH_AUTH_SOCK=/ssh-agent" pclaudeOne image, localhost/pclaude, built from node:22-slim. Everything is on PATH, so
Claude can reach for any of it:
| Node | node, npm — from the base image |
| Bun | bun |
| Deno | deno |
| Foundry | forge, cast, anvil, chisel |
| Go | go, with GOPATH=/home/node/go |
| Claude Code | claude |
| Shell tools | git, openssh, curl, jq, make, unzip, fzf, vim, less, ps |
That comes to about 1.3 GB, of which Claude Code's own binary is 277 MB and Node is 119 MB. The first build takes a few minutes; after that it is cached.
Pin a version at build time:
pclaude --pclaude-build --build-arg CLAUDE_CODE_VERSION=2.1.223
pclaude --pclaude-build --build-arg GO_VERSION=1.24.5Claude Code self-updates inside a running container, but the change is lost when it
exits. Run pclaude --pclaude-update now and then to bake in the current release.
Claude Code installs last in the Dockerfile, so bumping it reuses the toolchain layers.
just testtest.sh sources pclaude for its functions and runs the macOS credential sync against
a fake security command, so your real Keychain is never touched. It needs macOS (it uses
the real plutil) and skips elsewhere.
rm ~/.local/bin/pclaude
rm -rf ~/.local/share/pclaude
podman rmi -f $(podman images localhost/pclaude -q)