Skip to content

Latest commit

 

History

133 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Running Claude Code in a Container

This guide shows how to run claude-code in a Podman container while preserving your configuration and working directory access.

Easy Setup (Recommended)

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.sh

This will:

  1. Build the container image if it doesn't exist
  2. Optionally create a YOLO shell function
  3. 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 ~/.claude directory 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-paths

Image Tags

The 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
yolo

The 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.

Git Worktree Support

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=error

Security note: Bind mounting the original repo exposes more files and allows modifications. The prompt helps prevent unintended access.

Project Configuration

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/config

The 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 .git directory)

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

First-Time Login

On your first run, you'll need to authenticate:

  1. Claude Code will display a URL like https://claude.ai/oauth/authorize?...
  2. Copy the URL and paste it into a browser on your host machine
  3. Complete the authentication in your browser
  4. 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.

Manual Setup

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-permissions

Or 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

⚠️ Note: This uses --dangerously-skip-permissions to bypass all permission prompts. This is safe in containerized environments where the container provides isolation from your host system.

What's Included

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-trixie

GPU / CUDA

yolo --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 GPU
kernel 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.

Command Breakdown

Default Behavior (Preserved Host Paths)

  • --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 config
  • claude --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.

Anonymized Paths (Old Behavior with --anonymized-paths)

When using --anonymized-paths, paths are mapped to generic container locations:

  • -v ~/.claude:/claude:Z: Mounts to /claude in container
  • -v "$(pwd):/workspace:Z": Mounts to /workspace in container
  • -w /workspace: Working directory is /workspace
  • -e CLAUDE_CONFIG_DIR=/claude: Config directory is /claude

Tips

  1. Persist configuration: The ~/.claude bind mount ensures your settings, API keys, and session history persist between container runs

  2. 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 --continue in 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 allows claude --continue to 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.

  3. File ownership: The --userns=keep-id flag ensures files created or modified inside the container will be owned by your host user, regardless of your UID

  4. 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 push operations will fail. You'll need to push from your host after Claude Code commits your changes.

  5. 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"

Security Considerations

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.

About

Use podman to safely run claude-code with permission bypass (YOLO mode)

Resources

Stars

13 stars

Watchers

2 watching

Forks

Releases

Packages

Used by

Contributors

Languages