Skip to content

Instantly share code, notes, and snippets.

@itsjustcon
Created March 9, 2026 11:33
Show Gist options
  • Select an option

  • Save itsjustcon/7160aa224e8b41fcd163a661edb7f15b to your computer and use it in GitHub Desktop.

Select an option

Save itsjustcon/7160aa224e8b41fcd163a661edb7f15b to your computer and use it in GitHub Desktop.
Run Claude Code with --dangerously-skip-permissions safely inside Docker. Full autonomy for Claude, full isolation for you.

claude-safe

Run Claude Code with --dangerously-skip-permissions - safely - by isolating it inside a Docker container.

Claude Code's --dangerously-skip-permissions flag is powerful: it lets Claude run commands, edit files, and install packages without asking. But on your bare metal machine, that's a lot of trust. claude-safe gives you the best of both worlds: full autonomy for Claude, full isolation for you.

What it does

  • Builds a Docker image with Node.js 22, git, Python 3, build-essential, and other common dev tools
  • Mounts your project directory into the container (read-write)
  • Links your ~/.claude/ config (settings, memory, project context)
  • Passes your MAX account credentials read-only (or ANTHROPIC_API_KEY if you use that instead)
  • Runs claude --dangerously-skip-permissions inside the container
  • Container auto-deletes when you exit — nothing lingers

Claude can go wild inside the container. It can't touch anything outside your mounted project directory.

Install

# Download the script
curl -fsSL https://gist.githubusercontent.com/<YOUR_GIST_URL>/raw/claude-safe -o ~/.local/bin/claude-safe

# Make it executable
chmod +x ~/.local/bin/claude-safe

Make sure ~/.local/bin is in your PATH. If it isn't, add this to your ~/.zshrc or ~/.bashrc:

export PATH="$HOME/.local/bin:$PATH"

Requirements

  • Docker
  • A Claude Code account (MAX or API key)

Usage

# Run in the current directory
claude-safe

# Run in a specific project
claude-safe ~/projects/my-app

# Pass arguments to claude
claude-safe . -- -p "refactor the auth module"

# Force rebuild the Docker image (e.g. after a new claude-code release)
claude-safe --rebuild

# Help
claude-safe --help

What's in the container

Tool Version
Node.js 22 (LTS)
Python 3
git latest
build-essential gcc, g++, make
cmake latest
ripgrep, fd, jq, tree latest
vim latest
Claude Code latest (via npm)

How auth works

The script auto-detects your auth method:

  • MAX account: Mounts ~/.claude.json into the container as read-only. Claude can authenticate but can't modify your tokens.
  • API key: Pass ANTHROPIC_API_KEY as an env var and it gets forwarded into the container.

FAQ

Can Claude break out of the container? Docker provides process and filesystem isolation. Claude can only access your mounted project directory and (read-only) config files. It cannot access your home directory, other projects, or system files.

Will my changes persist? Yes — your project directory is mounted read-write, so all file changes Claude makes are written directly to your local filesystem. The container itself is ephemeral (--rm).

Can I customize the Docker image? The Dockerfile is embedded in the script. Edit the build_image() function to add languages, tools, or dependencies you need.

Why UID 1000? The container user is created with UID 1000, which matches the default user ID on macOS and most Linux systems. This ensures volume-mounted files have correct ownership.

#!/usr/bin/env bash
set -euo pipefail
IMAGE_NAME="claude-safe"
CONTAINER_NAME="claude-safe-$$"
usage() {
echo "Usage: claude-safe [directory] [-- claude args...]"
echo ""
echo "Launch Claude Code with --dangerously-skip-permissions inside a Docker container."
echo ""
echo "Arguments:"
echo " directory Project directory to mount (default: current directory)"
echo " -- args Additional arguments passed to claude"
echo ""
echo "Options:"
echo " --rebuild Force rebuild the Docker image"
echo " -h, --help Show this help message"
exit 0
}
build_image() {
local force="${1:-false}"
if [ "$force" = "true" ] || ! docker image inspect "$IMAGE_NAME" &>/dev/null; then
echo "Building $IMAGE_NAME Docker image..."
docker build -t "$IMAGE_NAME" -f - "$(mktemp -d)" <<'DOCKERFILE'
FROM node:22-bookworm
RUN apt-get update && apt-get install -y --no-install-recommends \
git \
build-essential \
cmake \
python3 \
python3-pip \
python3-venv \
curl \
wget \
jq \
ripgrep \
fd-find \
tree \
zip \
unzip \
openssh-client \
ca-certificates \
less \
vim \
&& rm -rf /var/lib/apt/lists/*
RUN npm install -g @anthropic-ai/claude-code
# Run as non-root user with a fixed UID so volume mounts work
RUN groupadd -g 1000 claude && useradd -m -u 1000 -g claude claude
USER claude
WORKDIR /workspace
ENTRYPOINT ["claude"]
DOCKERFILE
echo "Image built successfully."
fi
}
# --- Parse arguments ---
REBUILD=false
PROJECT_DIR=""
CLAUDE_ARGS=()
while [[ $# -gt 0 ]]; do
case "$1" in
--rebuild)
REBUILD=true
shift
;;
-h|--help)
usage
;;
--)
shift
CLAUDE_ARGS+=("$@")
break
;;
-*)
CLAUDE_ARGS+=("$1")
shift
;;
*)
if [ -z "$PROJECT_DIR" ]; then
PROJECT_DIR="$1"
else
CLAUDE_ARGS+=("$1")
fi
shift
;;
esac
done
# Default to current directory
PROJECT_DIR="${PROJECT_DIR:-.}"
PROJECT_DIR="$(cd "$PROJECT_DIR" && pwd)"
if [ ! -d "$PROJECT_DIR" ]; then
echo "Error: '$PROJECT_DIR' is not a directory" >&2
exit 1
fi
# --- Build image if needed ---
build_image "$REBUILD"
# --- Assemble docker run args ---
DOCKER_ARGS=(
run --rm -it
--name "$CONTAINER_NAME"
-v "$PROJECT_DIR:/workspace"
)
# Mount claude config directory
if [ -d "$HOME/.claude" ]; then
DOCKER_ARGS+=(-v "$HOME/.claude:/home/claude/.claude")
fi
# Mount auth credentials (MAX account OAuth tokens)
if [ -f "$HOME/.claude.json" ]; then
DOCKER_ARGS+=(-v "$HOME/.claude.json:/home/claude/.claude.json:ro")
fi
# Pass through API key if set
if [ -n "${ANTHROPIC_API_KEY:-}" ]; then
DOCKER_ARGS+=(-e "ANTHROPIC_API_KEY")
fi
# Pass through common env vars
for var in CLAUDE_MODEL CLAUDE_CODE_MAX_THINKING_TOKENS; do
if [ -n "${!var:-}" ]; then
DOCKER_ARGS+=(-e "$var")
fi
done
# --- Run ---
exec docker "${DOCKER_ARGS[@]}" "$IMAGE_NAME" \
--dangerously-skip-permissions \
"${CLAUDE_ARGS[@]}"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment