A feature-rich containerized development environment for safely using Claude Code CLI and other AI assistants in "dangerous mode" for multi-language development.
This Podman/Docker container provides an isolated environment where Claude Code and other AI assistants can operate with elevated permissions (--dangerously-skip-permissions) without compromising your host system. The container includes a comprehensive development toolchain for modern software development.
Two container variants are available:
| Container | Script | Image | Description |
|---|---|---|---|
| localdev (default) | localdev |
localdev:latest |
Lightweight, fast-starting container with Node.js LTS, Go, and essential tools |
| localfull | localfull |
localfull:latest |
Full-featured container with Java 17, Atlassian CLI, multiple Node.js versions |
- Debian Bookworm slim base (fast startup)
- Node.js LTS only (via nvm)
- Go toolchain with essential tools
- Claude Code CLI, TypeScript, pnpm, eslint, prettier
- No Java, no Atlassian CLI
- Eclipse Temurin 17 JDK base
- Multiple Node.js versions (14, 18, LTS)
- Full Go toolchain with all tools
- Atlassian CLI (acli)
- Marp CLI, mermaid-cli, Hugo, and more
- Security: Non-root user (developer) for best practices
- Architecture Support: AMD64 and ARM64
- USB Passthrough: Access to
/dev/bus/usbfor hardware development (Linux only, automatically disabled on macOS)
- Full Go toolchain with proper GOPATH configuration
- Development tools: goimports, godoc, gofumpt, govulncheck
- Linters: golangci-lint, staticcheck
- Debugging: Delve (dlv)
- Code generation: wire, gomodifytags, impl, gotests
- Mock generation: mockgen
- Formatting: golines (optional, may fail on some architectures)
- localdev: Node.js LTS only
- localfull: Node.js 14.16.0, 18.18.2, and LTS
- Package managers: npm, pnpm
- TypeScript with ts-node
- Code quality: eslint, prettier
- localfull only: webpack, esbuild, jest, vitest, nodemon, npm-check-updates
- Python 3 with pip
- uv package manager
- md2pdf tool
- Eclipse Temurin JDK 17 (base image, provides JVM for Atlassian CLI and other tools)
- Claude Code CLI (
@anthropic-ai/claude-code)- All invocations automatically include
--add-dir /claudefor global configuration - Convenient alias:
clauded(runs with--dangerously-skip-permissions) - Alternative alias:
copilotd(runs with--allow-all-tools)
- All invocations automatically include
- GitHub Copilot CLI (
@github/copilot)
- Version Control: Git, GitHub CLI (gh)
- Atlassian: Atlassian CLI (acli) - localfull only
- Containerization: Podman with rootless configuration
- Documentation: Marp CLI, mermaid-cli, md-to-pdf, pdf2md, Hugo - localfull only
- Utilities: jq, tree, curl, build-essential, file, xxd, zip, unzip
- Multimedia: ffmpeg, imagemagick, qpdf
- Network: libpcap-dev
- USB: usbutils, libusb-1.0, udev
- Package Management: Homebrew - localfull only
This project uses Podman instead of Docker for license compatibility and rootless container support.
# Ubuntu/Debian
sudo apt-get install podman
# Or use the Makefile
make preSee podman.io for other platforms.
On macOS, Podman runs inside a virtual machine. The default memory allocation (2GB) is insufficient for building this container, which includes memory-intensive Go tool compilations (particularly buf and protoc-gen-go).
Recommended: 16GB for optimal build performance. Adjust based on your host system's available RAM.
# Stop the Podman machine
podman machine stop
# Set memory to 16GB (16384 MB)
podman machine set --memory 16384
# Start the machine
podman machine start
# Verify the configuration
podman machine listGuidelines for memory allocation:
- 16GB or less host RAM: Allocate 8GB (
--memory 8192) - 32GB or more host RAM: Allocate 16GB (
--memory 16384) (recommended) - 64GB or more host RAM: Allocate 24GB+ (
--memory 24576) for maximum performance
If you encounter "signal: killed" errors during Go tool installations, your Podman machine memory is too low.
make defaultThis builds the lightweight localdev:latest container with:
- Memory limit: 16GB
- Automatic architecture detection (amd64/arm64)
- Pull latest base images
make fullThis builds the localfull:latest container with Java, Atlassian CLI, and additional tools.
make allmake no-cache # Both containers
make no-cache-default # Default container only
make no-cache-full # Full container only# Detect architecture
ARCH=$(uname -m)
if [ "$ARCH" = "x86_64" ]; then TARGETARCH=amd64; else TARGETARCH=arm64; fi
# Build default (lightweight) container
podman build -t localdev:latest --memory=16g --build-arg TARGETARCH=$TARGETARCH --pull .
# Build full container
podman build -t localfull:latest --memory=16g --build-arg TARGETARCH=$TARGETARCH --pull -f Dockerfile.full .Both scripts provide convenient access to their respective containers with automatic directory mounting and support for read-only external directories.
localdev- launches the lightweight container (default)localfull- launches the full-featured container with Java and Atlassian CLI
# Copy both to your bin directory
make install
# Or manually
cp localdev localfull ~/bin/
chmod +x ~/bin/localdev ~/bin/localfull# Run in current directory
./localdev
# Or if installed
localdevThis mounts your current working directory into the container at /<directory-name>.
The first run of a new container will be noticeably slow (30-60+ seconds) due to:
- User namespace setup: Podman's
--userns=keep-idcreates UID/GID mappings on first use - Overlay filesystem initialization: The container's layered filesystem requires initial setup
- Device permission checks: USB passthrough (
--device /dev/bus/usb) adds overhead
Subsequent runs are much faster as these mappings and caches persist between sessions.
The localdev script creates the following mount structure:
Container Filesystem
├── /<project-name>/ # Your current directory (read-write)
├── /claude/ # Host ~/.claude directory (read-write)
│ ├── CLAUDE.md # Global Claude instructions
│ ├── settings.json # Claude settings
│ └── ... # Other Claude configuration
└── /external/ # Additional read-only mounts
├── repo1/
├── repo2/
└── ...
The script automatically mounts your host's ~/.claude directory to /claude inside the container:
- Location:
$HOME/.claude→/claude - Permissions: Read-write
- Auto-integration: The Claude CLI wrapper automatically adds
--add-dir /claudeto all invocations
This enables:
- Global
CLAUDE.mdinstructions available in all projects - Persistent Claude settings across sessions
- Shared slash commands and configurations
If ~/.claude doesn't exist on the host, the mount is skipped with an info message.
The script supports mounting additional directories as read-only inside the container at /external/<directory-name>.
# Mount single external directory
./localdev /path/to/external/repo
# Mount multiple external directories
./localdev /path/to/repo1 /path/to/repo2 /path/to/repo3Example:
./localdev /home/user/reference-code /home/user/docsThis creates:
/myproject→ your current directory (read-write)/claude→~/.claude(read-write)/external/reference-code→/home/user/reference-code(read-only)/external/docs→/home/user/docs(read-only)
Set the LOCALDEV_MOUNTS environment variable with semicolon-separated paths:
# Single session
LOCALDEV_MOUNTS="/path/to/repo1;/path/to/repo2" ./localdev
# Persistent (add to ~/.bashrc or ~/.zshrc)
export LOCALDEV_MOUNTS="/home/user/reference-code;/home/user/docs"
./localdevYou can use both command line arguments and the environment variable together:
export LOCALDEV_MOUNTS="/home/user/common-libs"
./localdev /home/user/project-specific-ref- Paths must be absolute (not relative)
- Directories must exist
- Invalid paths are skipped with warnings
# Using Makefile (default container)
make run
# Full container
make run-full
# Or manually
podman run --rm -it -v "$(pwd):/workspace" localdev:latest bash # default
podman run --rm -it -v "$(pwd):/workspace" localfull:latest bash # full# Mount specific project directory
podman run --rm -it -v "/path/to/project:/workspace" localdev:latest bash
# Multiple mounts
podman run --rm -it \
-v "$(pwd):/workspace" \
-v "/path/to/libs:/libs:ro" \
-v "$HOME/.claude:/claude:rw" \
localdev:latest bash# Standard invocation (automatically includes --add-dir /claude)
claude
# With convenient alias (dangerous mode + --add-dir /claude)
clauded
# Or full command
claude --dangerously-skip-permissions
# Alternative for copilot compatibility
copilotdNote: The claude command wrapper automatically adds --add-dir /claude to all invocations, ensuring your global Claude configuration is always available.
If you mounted external directories using the localdev script:
# List external directories
ls -la /external/
# Access specific external directory
cd /external/reference-code
cat /external/docs/api-spec.mdMultiple Node.js versions are only available in the localfull container:
# Switch Node.js versions
nvm use 14
nvm use 18
nvm use default # LTS
# List installed versions
nvm listNote: The default localdev container only includes Node.js LTS.
The container includes USB passthrough for hardware development on Linux systems. USB passthrough is automatically disabled on macOS as /dev/bus/usb is not available on that platform.
# List USB devices (Linux only)
lsusb
# Access USB devices for development
# (requires appropriate permissions on host)Note: On macOS, USB device access from within the container is not supported due to platform limitations.
# Start container with external reference code
./localdev /home/user/api-reference
# Inside container
cd /myproject
# Your global CLAUDE.md is available
cat /claude/CLAUDE.md
# Access external reference
cat /external/api-reference/examples/auth.go
# Use Claude to help with development
clauded
# Build and test
go build ./...
go test ./...
npm test- Filesystem: Only mounted directories are accessible
- Network: Isolated container network
- User: Runs as non-root
developeruser (UID/GID 1000) - User Namespace:
--userns=keep-idensures files created in container have correct host ownership - Cleanup: Use
--rmflag for automatic container removal
External directories mounted by the localdev script are read-only, preventing accidental modifications to reference code or shared libraries.
The container isolates AI assistants' file operations from your host system. Even in "dangerous mode," Claude can only affect files within mounted directories.
- Only mount directories you need
- Use read-only mounts (
:ro) for reference materials - Review changes before committing from host
- Don't mount sensitive directories like
~/.sshunless necessary - Use version control for all work
/
├── <project-name>/ # Dynamic working directory (your pwd)
├── claude/ # Global Claude configuration (from ~/.claude)
│ ├── CLAUDE.md # Global instructions
│ ├── settings.json # Claude settings
│ └── commands/ # Custom slash commands
├── external/ # External read-only mounts (via localdev script)
│ ├── repo1/
│ ├── repo2/
│ └── ...
├── usr/local/go/ # Go installation
├── usr/local/nvm/ # Node.js version manager
├── home/developer/ # Developer user home
│ ├── go/ # GOPATH
│ │ ├── bin/
│ │ ├── pkg/
│ │ └── src/
│ └── .local/bin/ # uv and tools
└── home/linuxbrew/ # Homebrew installation
The container sets up these key environment variables:
GOROOT=/usr/local/go
GOPATH=/home/developer/go
NVM_DIR=/usr/local/nvm
PATH includes:
- /usr/local/go/bin
- /home/developer/go/bin
- /usr/local/nvm/versions/node/<version>/bin
- /home/developer/.local/bin
- /home/linuxbrew/.linuxbrew/binThe localdev script runs the container with these options:
| Option | Purpose |
|---|---|
--userns=keep-id |
Maps container user to host user for correct file ownership |
--device /dev/bus/usb |
Enables USB device passthrough (Linux only, automatically skipped on macOS) |
--group-add keep-groups |
Preserves host group memberships |
-e HOST_UID/HOST_GID |
Passes host user IDs for reference |
Out of Memory (OOM) errors during build:
- macOS users: The Podman machine needs sufficient memory. See Configuring Podman Machine Memory for setup instructions.
- The Makefile sets
--memory=16gfor the build process - The default
localdevcontainer requires less memory thanlocalfull - If you see "signal: killed" during Go tool installations when building
localfull, increase Podman machine memory to 16GB - Symptoms include compilation failures for
buf,protoc-gen-go, or other Go tools
Architecture detection fails:
- Manually set
TARGETARCH=amd64orTARGETARCH=arm64
External mount not appearing:
- Verify the path is absolute
- Check the directory exists on host
- Look for warning messages from the script
Permission denied errors:
- Ensure mounted directories have appropriate read permissions
- The container runs as UID/GID 1000
- The
--userns=keep-idoption should map permissions correctly
Command not found inside container:
- For npm packages: ensure nvm is loaded (
. $NVM_DIR/nvm.sh) - For Go tools: check
$GOPATH/binis in PATH - The container's
.bashrcshould load these automatically
Claude global config not loading:
- Verify
~/.claudeexists on your host - Check the mount message when starting the container
- The
/claudedirectory should be visible inside the container
USB devices not accessible:
- USB passthrough only works on Linux
- On macOS, USB device access from containers is not supported
- On Linux: ensure USB devices are connected before starting the container
- On Linux: check host permissions on
/dev/bus/usb - May require running podman with additional privileges on Linux
This container is ideal for:
- AI-assisted development with Claude Code or GitHub Copilot
- Multi-language projects (Go + TypeScript/Node.js)
- Safe experimentation with AI code generation
- Isolated build and test environments
- Cross-referencing multiple codebases safely
- Hardware/USB development projects
Use localfull for:
- Java/JVM development
- Atlassian CLI integration (Jira, Confluence)
- Documentation generation (Marp, Mermaid, Hugo)
- Projects requiring multiple Node.js versions
This project uses Podman instead of Docker to avoid Docker Desktop licensing requirements for commercial use. Podman is fully open-source and compatible with Docker images and commands.
When modifying the container:
- Test builds on both AMD64 and ARM64 if possible
- Keep memory-intensive operations batched (see npm installs)
- Update this README with new features
- Maintain the security-first approach