This guide shows how to run claude-code in a Podman container while preserving your configuration and working directory access.
Clone the repository and run the setup script to build the container and optionally create a YOLO command:
git clone https://github.com/con/yolo
cd yolo
./setup-yolo.shThis will:
- Build the container image if it doesn't exist
- Optionally create a
YOLOshell function - Configure everything for you
After setup, just run yolo from any directory to start Claude Code in YOLO mode!
By default, yolo preserves your original host paths to ensure session compatibility with native Claude Code. This means:
- Your
~/.claudedirectory is mounted at its original path - Your current directory is mounted at its original path (not
/workspace) - Sessions created in the container can be resumed in your native environment and vice versa
If you prefer the old behavior with anonymized paths (/claude and /workspace), use the --anonymized-paths flag:
yolo --anonymized-pathsThe container image is con-bomination-claude-code and is tagged latest by
default. Both scripts accept a --tag option so you can build and run
alternative images (e.g. to test a Dockerfile change) without clobbering the
image you use every day:
# Build a test image as con-bomination-claude-code:test,
# leaving :latest and ~/.local/bin/yolo untouched
./setup-yolo.sh --build=yes --install=no --tag=test
# Run it
yolo --tag=test
# Back to the default image
yoloThe tag can also be set per project or user-wide via YOLO_IMAGE_TAG in the
config file (or in the environment); --tag overrides both.
When running in a git worktree, yolo can detect and optionally bind mount the original repository. This allows Claude to access git objects and perform operations like commit and fetch. Control this behavior with the --worktree option:
--worktree=ask(default): Prompts whether to bind mount the original repo--worktree=bind: Automatically bind mounts the original repo--worktree=skip: Skip bind mounting and continue normally--worktree=error: Exit with error if running in a worktree
# Prompt for bind mount decision (default)
yolo
# Always bind mount in worktrees
yolo --worktree=bind
# Skip bind mounting, continue normally
yolo --worktree=skip
# Disallow running in worktrees
yolo --worktree=errorSecurity note: Bind mounting the original repo exposes more files and allows modifications. The prompt helps prevent unintended access.
You can create a per-project configuration file to avoid repeating command line options. The config is auto-created on first run, or you can use yolo --install-config:
# Auto-creates .git/yolo/config on first run in a git repo
yolo
# Or manually install/view config
yolo --install-config
# Edit with your preferences
vi .git/yolo/configThe configuration file is stored in .git/yolo/ which means:
- It won't be tracked by git
- It won't be destroyed by
git clean - It works correctly with git worktrees (they all reference the same
.gitdirectory)
Example configuration (.git/yolo/config):
# Volume mounts with shorthand syntax
YOLO_PODMAN_VOLUMES=(
"~/projects" # Mounts ~/projects at same path in container
"~/data::ro" # Mounts ~/data read-only at same path
)
# Additional podman options
YOLO_PODMAN_OPTIONS=(
"--env=DEBUG=1"
)
# Arguments for claude
YOLO_CLAUDE_ARGS=(
"--model=claude-3-opus-20240229"
)
# Default flags
USE_NVIDIA=1
# Image tag to run (default: latest)
YOLO_IMAGE_TAG="test"Volume shorthand syntax:
"~/projects"→~/projects:~/projects:Z(1-to-1 mount)"~/data::ro"→~/data:~/data:ro,Z(1-to-1 with options)"~/data:/data:Z"→~/data:/data:Z(explicit, unchanged)
Use --no-config to ignore the configuration files entirely.
Note on precedence: config files are sourced after the command line is parsed,
so USE_ANONYMIZED_PATHS, USE_NVIDIA, and WORKTREE_MODE set in a config
file override the matching command line flag. YOLO_IMAGE_TAG is the
exception — --tag always wins over the config files and the environment.
See config.example for a complete configuration template with detailed comments.
TODO: Add curl-based one-liner setup once this PR is merged
On your first run, you'll need to authenticate:
- Claude Code will display a URL like
https://claude.ai/oauth/authorize?... - Copy the URL and paste it into a browser on your host machine
- Complete the authentication in your browser
- Copy the code from the browser and paste it back into the container terminal
Your credentials are stored in ~/.claude on your host, so you only need to login once. Subsequent runs will use the stored credentials automatically.
If you prefer to run commands manually, first build the image from the images/ directory:
podman build --build-arg TZ=$(timedatectl show --property=Timezone --value) -t con-bomination-claude-code:latest images/(Use another tag, e.g. -t con-bomination-claude-code:test, to keep test images
around; yolo --tag=test then runs it.)
Then run (with original host paths preserved by default):
podman run -it --rm \
--userns=keep-id \
-v "$HOME/.claude:$HOME/.claude:Z" \
-v ~/.gitconfig:/tmp/.gitconfig:ro,Z \
-v "$(pwd):$(pwd):Z" \
-w "$(pwd)" \
-e CLAUDE_CONFIG_DIR="$HOME/.claude" \
-e GIT_CONFIG_GLOBAL=/tmp/.gitconfig \
con-bomination-claude-code:latest \
claude --dangerously-skip-permissionsOr with anonymized paths (old behavior):
podman run -it --rm \
--userns=keep-id \
-v ~/.claude:/claude:Z \
-v ~/.gitconfig:/tmp/.gitconfig:ro,Z \
-v "$(pwd):/workspace:Z" \
-w /workspace \
-e CLAUDE_CONFIG_DIR=/claude \
-e GIT_CONFIG_GLOBAL=/tmp/.gitconfig \
con-bomination-claude-code:latest \
claude --dangerously-skip-permissions--dangerously-skip-permissions to bypass all permission prompts. This is safe in containerized environments where the container provides isolation from your host system.
The Dockerfile is based on Anthropic's official setup and includes Claude Code CLI plus common development tools. See the Dockerfile for the complete list.
The base image is node:24-trixie — Node 24 (current LTS) on Debian 13
"trixie", which provides git 2.47 (Debian 12 "bookworm" shipped 2.39, too old
for git worktree add --orphan). Override it with --base-image= if you need a
different base:
./setup-yolo.sh --build=yes --base-image=node:22-trixieyolo --nvidia passes the host GPU through via CDI, which injects the host's
NVIDIA driver libraries into the container — so the image itself must not ship a
driver of its own.
yolo --nvidia does not bring nvidia-smi with it, so to confirm the
passthrough actually worked, run the bundled probe — Python standard library
only, no toolkit or pip packages needed:
tools/gpu-check.py # human-readable report
tools/gpu-check.py --json # machine-readable
tools/gpu-check.py --quiet # exit code only: 0 usable, 1 broken, 2 no GPUkernel driver: 590.48.01
device nodes: nvidia-modeset, nvidia-uvm, nvidia-uvm-tools, nvidia0, nvidiactl
CUDA driver API: 13.1
GPU 0: NVIDIA A100-PCIE-40GB
compute capability 8.0 (108 SMs)
memory 40441 MiB total, 40008 MiB free
telemetry 5% busy, 33 C, 42.5 W
context create OK
device allocation OK (64 MiB)
host<->device copy OK
device-side memset OK
kernel launch OK (1048576 elements, JIT from PTX ISA 9.0)
GPU compute is available.
It goes past enumeration on purpose: it creates a context, copies data both ways, and JIT-compiles and runs a real kernel, since GPUs routinely enumerate fine while compute fails on a driver/library mismatch or a bad device mount.
No CUDA toolkit is installed in the image, deliberately. Everything above works on a stock build: CDI supplies the driver libraries, and that is all a GPU workload needs at runtime — PyTorch, JAX and friends ship their own CUDA runtime inside their wheels. Shipping a toolkit would add several GB and risk shadowing the injected host driver with a mismatched userspace, which is exactly the breakage reported in #79.
--extras=cuda and --cuda-version= are still accepted so old invocations do
not break, but they install nothing and print a note saying so. The only thing
you lose is nvcc for compiling CUDA sources inside the container; if you
need that, install it yourself — Debian's nvidia-cuda-toolkit is in non-free,
which this image does not enable, so it means a local Dockerfile edit or an
nvcc wheel via uv/pip.
--extras, --packages and --base-image are not recorded
anywhere: a later ./setup-yolo.sh --build=yes without them rebuilds the image
with the defaults, so pass the same flags each time you rebuild.
--userns=keep-id: Maps your host user ID inside the container so files are owned correctly-v "$HOME/.claude:$HOME/.claude:Z": Bind mounts your Claude configuration directory at its original path with SELinux relabeling-v ~/.gitconfig:/tmp/.gitconfig:ro,Z: Mounts git config read-only for commits (push operations not supported)-v "$(pwd):$(pwd):Z": Bind mounts your current working directory at its original path-w "$(pwd)": Sets the working directory inside the container to match your host path-e CLAUDE_CONFIG_DIR="$HOME/.claude": Tells Claude Code where to find its configuration (at original path)-e GIT_CONFIG_GLOBAL=/tmp/.gitconfig: Points git to the mounted configclaude --dangerously-skip-permissions: Skips all permission prompts (safe in containers)--rm: Automatically removes the container when it exits-it: Interactive terminal
This default behavior ensures that session histories and project paths are compatible between containerized and native Claude Code environments.
When using --anonymized-paths, paths are mapped to generic container locations:
-v ~/.claude:/claude:Z: Mounts to/claudein container-v "$(pwd):/workspace:Z": Mounts to/workspacein container-w /workspace: Working directory is/workspace-e CLAUDE_CONFIG_DIR=/claude: Config directory is/claude
-
Persist configuration: The
~/.claudebind mount ensures your settings, API keys, and session history persist between container runs -
Session compatibility: By default, paths are preserved to match your host environment. This means:
- Sessions created in the container can be resumed outside the container using
claude --continuein your native environment - Each project maintains its own session history based on its actual path (e.g.,
/home/user/project) - You can seamlessly switch between containerized and native Claude Code for the same project
Note: With
--anonymized-paths, all projects appear to be in/workspace, which allowsclaude --continueto retain context across different projects that were also run with this flag. This can be useful for maintaining conversation context when working on related codebases. - Sessions created in the container can be resumed outside the container using
-
File ownership: The
--userns=keep-idflag ensures files created or modified inside the container will be owned by your host user, regardless of your UID -
Git operations: Git config is mounted read-only, so Claude Code can read your identity and make commits. However, SSH keys are not mounted, so
git pushoperations will fail. You'll need to push from your host after Claude Code commits your changes. -
Multiple directories: Mount additional directories as needed:
yolo -v ~/projects:~/projects -v ~/data:~/data -- "help with this code"
Or with anonymized paths:
yolo --anonymized-paths -v ~/projects:/projects -v ~/data:/data -- "help with this code"
YOLO mode runs Claude Code with --dangerously-skip-permissions, providing unrestricted command execution within the container. The container provides isolation through:
- Filesystem boundaries: Only
~/.claude,~/.gitconfig, and your current working directory are accessible to Claude - Process isolation: Rootless podman user namespace isolation (
--userns=keep-id) - Limited host access: SSH keys and other sensitive files are not mounted
What is NOT restricted:
- Network access: Claude can make arbitrary network connections from within the container (to package registries, APIs, external services, etc.)
When to be cautious:
- Untrusted repositories: Malicious code comments or documentation could exploit prompt injection to trick Claude into executing harmful commands or exfiltrating data
- Mounting directories with sensitive data (credentials, private keys, confidential files)
- Projects that access production systems or databases
For higher security needs, consider running untrusted code in a separate container without mounting sensitive directories, or wait for integration with Anthropic's modern sandbox runtime which provides network-level restrictions.