A rootless Podman container setup for running Claude Code CLI in a secure, isolated Ubuntu environment with built-in sandbox mode support.
- Rootless Podman: No sudo required for container operations
- Ubuntu 24.04 LTS: Latest long-term support release
- Multi-container support: Run multiple named containers simultaneously
- Sandbox Mode: Built-in support for Claude Code's
/sandboxmode using bubblewrap - Auto-updates: Claude Code installed as non-root user to enable automatic updates
- Passwordless sudo: Convenient sudo access inside the container
- Dynamic UID/GID mapping: Automatically matches your host user for seamless file permissions
- Shared folder mounting: Mount any directory with proper SELinux compatibility
- Permission skip alias: Pre-configured alias for
--allow-dangerously-skip-permissionsflag - Test mode: Separate test containers/images for safe development
- Podman (rootless mode configured)
- Linux system (tested on Ubuntu/Fedora/RHEL-based distributions)
- User UID/GID (typically 1000 on most systems)
Ubuntu/Debian:
sudo apt-get update
sudo apt-get install podmanFedora:
sudo dnf install podmanArch Linux:
sudo pacman -S podmanBuild the image with your user's UID/GID automatically detected:
./scripts/build.shThis will:
- Download Ubuntu 24.04 base image
- Install required dependencies (bubblewrap, socat, git, etc.)
- Create a
claudeuser matching your UID/GID - Install Claude Code CLI as the
claudeuser - Configure passwordless sudo
- Set up the
claudecommand alias with--allow-dangerously-skip-permissions
Build time: ~5-10 minutes (depending on network speed)
Start the container with a shared folder:
# Use current directory with default name
./scripts/run.sh
# Specify a custom directory
./scripts/run.sh /path/to/your/project
# Use a named instance for multiple containers
./scripts/run.sh --name myproject /path/to/your/projectThis creates and starts a container with:
- Container name:
claude-sandbox-default(orclaude-sandbox-<name>with--name) - Shared folder mounted at
/workspaceinside the container - SELinux compatibility (
:Zflag) - UID/GID mapping for seamless file permissions
Open an interactive shell inside the running container:
# Auto-detect (works if only one container running)
./scripts/exec.sh
# Or specify by name
./scripts/exec.sh --name myprojectYou'll be logged in as the claude user at /workspace (your shared folder).
Inside the container:
# The alias automatically adds --allow-dangerously-skip-permissions
claude
# Activate sandbox mode (requires bubblewrap and socat)
/sandbox
# Check version
claude --version
# Authenticate (first time only)
claude auth login
# Run Claude Code on your project
claude "help me refactor this code"All scripts support common flags (in any order):
--test- Use test image/containers instead of production--name <name>- Specify instance name (default:default)--force- Force operation where applicable--no-sudo- (build.sh only) Disable sudo access for claude user--no-cache- (build.sh only) Force rebuild without using cached layers
Builds the container image with your current user's UID/GID.
- Output: Container image tagged as
claude-sandbox(orclaude-sandbox-testwith--test) - Log file:
/tmp/claude-sandbox-build.log - Build args: Automatically passes
USER_UIDandUSER_GID - --no-sudo: Disable sudo access for claude user (more restrictive container)
- --no-cache: Force rebuild without using cached layers
Creates and starts a new container with optional shared folder path.
- Arguments:
--name <name>(optional): Instance name (default:default)path(optional): Directory to share (defaults to current directory)
- Container name:
claude-sandbox-<name>(orclaude-sandbox-test-<name>with--test) - Mount point:
/workspaceinside container - Note: Will fail if container already exists (use
./scripts/rm.shfirst)
Opens an interactive bash shell inside the running container.
- Auto-detect: If only one container is running and no
--namespecified, auto-selects it - Multiple containers: Lists running containers and asks you to specify
--name - User:
claude - Working directory:
/workspace
Stops a running container (preserves container filesystem for restart).
- Default name:
default - Note: Use
start.shto restart, orrm.shto remove
Restarts a stopped container.
- Default name:
default - Note: Container must exist (created with
run.sh)
Removes a container.
- Default name:
default - --force: Stop and remove even if running
Removes an image.
- Warns: If containers using the image still exist
- --force: Remove despite existing containers
Lists all claude-sandbox containers with status and shared folder path.
- Shows: Container name, running/stopped status, mounted folder
Targeted cleanup of claude-sandbox resources only.
- --test: Only removes test containers and test image (safe while running in production container)
- Without --test: Removes ALL claude-sandbox containers and both images
- --force: Skip confirmation prompt
Note: Unlike the old clean.sh, this only affects claude-sandbox resources, not your entire Podman environment.
The container uses --userns=keep-id with dynamic UID/GID mapping to ensure seamless file permissions:
| Location | User | Group | UID | GID |
|---|---|---|---|---|
| Inside container | claude |
claude |
Your UID | Your GID |
| On host | Your username | Your group | Your UID | Your GID |
Files created inside the container will appear on the host with your user ownership, and vice versa.
Host Machine (UID 1000, GID 1000)
└── Rootless Podman
├── Image: claude-sandbox (prod) / claude-sandbox-test (test)
└── Containers: claude-sandbox-<name> / claude-sandbox-test-<name>
├── Ubuntu 24.04 base
├── bubblewrap + socat (for Claude sandbox mode)
├── Claude Code CLI (installed as user, not root)
├── claude user (UID 1000, GID 1000 - matches host)
├── Passwordless sudo configured
├── /workspace → host shared folder (:Z for SELinux)
└── UID mapping via --userns=keep-id:uid=1000,gid=1000
Error: Container 'claude-sandbox-default' already exists.
Solution: Remove the existing container first:
./scripts/rm.sh
# Or force remove if running:
./scripts/rm.sh --forceIf claude command is not found inside the container:
-
Check if Claude Code installed correctly:
ls -la ~/.local/bin/claude -
Verify PATH includes Claude Code:
echo $PATH | grep ".local/bin"
-
Try rebuilding:
./scripts/rm.sh --force ./scripts/rmi.sh --force ./scripts/build.sh ./scripts/run.sh
If files have wrong ownership:
-
Verify your host UID/GID:
id
-
Check container user UID/GID:
podman exec claude-sandbox-default id claude -
Rebuild if they don't match:
./scripts/nuke.sh --force ./scripts/build.sh ./scripts/run.sh
Error: Sandbox requires socat and bubblewrap.
Solution: The packages are installed during build. If missing:
# Inside container
sudo apt-get update
sudo apt-get install bubblewrap socatIf you get permission denied errors on SELinux-enabled systems (Fedora, RHEL):
- Ensure you're using the
:Zflag (already included inrun.sh) - Check SELinux status:
sestatus - Temporarily test with SELinux permissive:
sudo setenforce 0(not recommended for production)
./scripts/list.sh./scripts/stop.sh --name myproject
# Or for default container:
./scripts/stop.sh./scripts/start.sh --name myproject./scripts/rm.sh --name myproject
# Force remove (even if running):
./scripts/rm.sh --name myproject --forcepodman logs claude-sandbox-myproject# Remove all claude-sandbox containers and images (with confirmation):
./scripts/nuke.sh
# Remove only test resources:
./scripts/nuke.sh --testRun separate containers for different projects:
# Start containers for different projects
./scripts/run.sh --name webapp ~/projects/webapp
./scripts/run.sh --name api ~/projects/api
./scripts/run.sh --name docs ~/projects/docs
# List all running containers
./scripts/list.sh
# Access specific container
./scripts/exec.sh --name webapp
# Stop one while keeping others running
./scripts/stop.sh --name apiUse --test flag to work with separate test containers/images:
# Build test image
./scripts/build.sh --test
# Run test container
./scripts/run.sh --test --name experiment /tmp/test
# Clean up only test resources (safe while in production)
./scripts/nuke.sh --test --forceEdit scripts/run.sh and add additional -v flags:
-v "/path/to/folder1:/folder1:Z" \
-v "/path/to/folder2:/folder2:Z" \The build automatically uses your current UID/GID. To use different values:
# Edit build.sh and replace id commands:
USER_UID=1001
USER_GID=1001- Sandbox mode: Uses bubblewrap for process isolation
- Rootless containers: Run without root privileges on the host
- Passwordless sudo: Enabled by default inside container for convenience (container is already isolated)
- Use
./scripts/build.sh --no-sudoto disable sudo entirely for maximum restriction
- Use
- Network access: Container has full network access (no filtering by default)
- Shared folders: Only explicitly mounted folders are accessible
Contributions welcome! Please ensure:
- Scripts remain POSIX-compliant where possible
- Documentation is updated for any new features
- UID/GID mapping works for different users
MIT License - See LICENSE file for details.
Created for running Claude Code CLI in an isolated, reproducible environment with proper sandbox support.