Megaclaude: Sandboxed Claude Code in Docker
Table of Contents
I love Claude Code. It’s become a core part of how I build things. But there’s a friction point that drives me nuts: the constant permission prompts.
Can I create this file? Can I run this command? Can I search the web? Can I install this package?
Yes. Yes to all of it. I gave you a task, go do it. I believe in you.
This is especially painful for greenfield projects. When I have an idea for something new and I want to quickly prototype it, iterate on it, see if the concept has legs, Claude Code is incredible. But greenfield work is where the permission prompts are at their absolute worst. Claude is constantly researching, pulling up library docs, creating new files from scratch, installing dependencies, running tests for the first time. Every single one of those actions triggers a prompt. You end up babysitting the thing, clicking “yes” every 30 seconds, which kind of defeats the purpose of having an AI assistant do the work. I feel like a drinky bird.

The permission system exists for good reasons. When Claude Code has access to your actual machine, your git credentials, your SSH keys, your filesystem, you want guardrails. But when I’m exploring an idea and I just want Claude to go heads-down and build something, those guardrails become a leash. I want to hand it a mission, walk away, make a coffee, and come back to results.
So I built megaclaude. It’s a bash script that locks Claude Code inside a Docker container with --dangerously-skip-permissions and lets it go absolutely feral in a sandbox where it can’t do anything too catastrophic. Think of it as a padded room for your AI coding assistant.
The Idea #
The concept is simple:
- Spin up a Docker container with your project directory mounted
- Install Claude Code and mise (for managing project toolchains) inside the container
- Launch Claude with
--dangerously-skip-permissionsso it never asks for confirmation - Keep everything else (git credentials, SSH keys, host filesystem) outside the container
Claude gets free reign within the sandbox. It can install packages, create files, run tests, modify code, hit the internet for documentation. But it can’t push to your repos, can’t touch anything outside /workspace, and can’t mess with your host machine. What happens in the container stays in the container. Mostly.
Is it perfectly safe? No, the container has internet access, which is a known trade-off. Claude needs the internet to look things up, and I’m okay with that risk. But the blast radius is contained to the project directory and a throwaway container. Claude can’t accidentally rm -rf / your system, can’t do a destructive git push --force to your repos, and can’t poke around in your other projects. If something goes sideways, docker rm and you’re back to clean.
The Script #
The whole thing is a single bash script. Let’s walk through it.
The Dockerfile (Generated at Runtime) #
Rather than maintaining a separate Dockerfile, megaclaude generates one in a temp directory at build time. The whole image definition lives inside the bash script. Turtles all the way down. The image is based on Ubuntu 24.04 with a reasonable set of development tools:
FROM ubuntu:24.04
ARG DEBIAN_FRONTEND=noninteractive
RUN apt-get update && apt-get install -y --no-install-recommends \
build-essential \
ca-certificates \
cmake \
curl \
file \
git \
gnupg \
jq \
less \
libssl-dev \
locales \
man-db \
openssh-client \
pkg-config \
python3 \
python3-pip \
python3-venv \
ripgrep \
software-properties-common \
sudo \
tree \
unzip \
vim \
wget \
zip \
zlib1g-dev \
&& rm -rf /var/lib/apt/lists/*
RUN locale-gen en_US.UTF-8
ENV LANG=en_US.UTF-8 LC_ALL=en_US.UTF-8
RUN mkdir -p /workspace
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
WORKDIR /workspace
ENTRYPOINT ["/entrypoint.sh"]
Nothing fancy. The image is a general-purpose dev environment. The interesting stuff happens in the entrypoint.
UID/GID Matching #
One detail that matters more than you’d think: the container creates a user that matches your host UID and GID. This means files Claude creates inside /workspace are owned by you on the host, not by root.
DEV_UID="${DEV_UID:-1000}"
DEV_GID="${DEV_GID:-1000}"
DEV_USER="${DEV_USER:-dev}"
if ! getent group "$DEV_GID" >/dev/null 2>&1; then
groupadd -g "$DEV_GID" "$DEV_USER" 2>/dev/null
fi
if ! id "$DEV_USER" >/dev/null 2>&1; then
useradd -m -u "$DEV_UID" -g "$DEV_GID" -s /bin/bash "$DEV_USER" 2>/dev/null
fi
Without this, you’d end up with root-owned files scattered across your project directory after every session. If you’ve ever done the sudo chown -R dance after a Docker mishap, you know the pain.
Persistent Home Volume #
Claude Code and mise are installed into the user’s home directory. Reinstalling them on every container start would be painful (about 30 seconds each time), so megaclaude uses a named Docker volume for the home directory:
HOME_VOLUME="megaclaude-home"
if ! docker volume inspect "$HOME_VOLUME" &>/dev/null; then
docker volume create "$HOME_VOLUME" >/dev/null
FIRST_RUN=true
else
# Volume exists, claude + mise already cached
fi
First run installs everything. Subsequent runs skip straight to launching Claude. The volume persists your Claude authentication too, so you only need to log in once.
Bootstrapping Tools #
On first run, the entrypoint installs Claude Code and mise into the persistent home volume:
if [[ ! -x "$CLAUDE_BIN" ]]; then
echo "⚡ First run - bootstrapping tools"
echo " [1/2] Installing Claude Code..."
run_as_user 'curl -fsSL https://claude.ai/install.sh | bash'
echo " [2/2] Installing mise..."
run_as_user 'curl -fsSL https://mise.run | sh'
fi
And if your project has a mise.toml or .tool-versions file, it automatically installs the project’s toolchain:
if [[ -f .mise.toml ]] || [[ -f .tool-versions ]] || [[ -f mise.toml ]]; then
$MISE_BIN trust 2>/dev/null
$MISE_BIN install --yes 2>/dev/null
fi
This is one of my favourite parts. I use mise for managing language runtimes across all my projects (Node versions, Go versions, Python versions, you name it), and having it as a first-class concept in megaclaude means Claude automatically gets access to the right toolchain for whatever project it’s working on. No manual setup, no “sorry, I don’t have Go installed” halfway through a task.
The Mission Briefing #
Before launching, the script prompts you for a mission. Think of it like giving a contractor a work order before locking them in the building:
step "Mission briefing"
echo " What should Claude work on? Describe the task, goal, or"
echo " feature you want built. Be as specific as you like."
echo " (Empty = interactive mode, no initial prompt)"
echo " Mission:"
IFS= read -r MISSION
if [[ -n "$MISSION" ]]; then
echo " Any constraints, preferences, or context? (optional)"
IFS= read -r CONTEXT
if [[ -n "$CONTEXT" ]]; then
MISSION="${MISSION}
Additional context: ${CONTEXT}"
fi
fi
If you provide a mission, it gets injected into a CLAUDE.md file in the container’s home directory, which Claude Code reads automatically on startup:
if [[ -n "${MEGA_MISSION:-}" ]]; then
mkdir -p "$HOME_DIR/.claude"
cat > "$HOME_DIR/.claude/CLAUDE.md" <<MISSION_EOF
# MEGACLAUDE SESSION
You are running in a fully sandboxed Docker container with --dangerously-skip-permissions.
You have complete freedom to read, write, execute, and install anything — nothing can break.
Be bold and autonomous. Do not ask for permission or confirmation — just do the work.
## Your Mission
${MEGA_MISSION}
---
Work autonomously until the mission is fully complete. Show your work as you go.
MISSION_EOF
fi
This is the key trick. The CLAUDE.md file tells Claude it’s in a sandbox and should act accordingly: no hesitation, no asking for permission, just execute. Combined with --dangerously-skip-permissions, Claude just… goes. No more drinky bird.
Launching the Container #
Finally, everything comes together in the docker run:
exec docker run -it --rm \
--name "$CONTAINER_NAME" \
-v "${PROJECT_DIR}:/workspace" \
-v "${HOME_VOLUME}:/home/${HOST_USER}" \
-e "DEV_UID=${HOST_UID}" \
-e "DEV_GID=${HOST_GID}" \
-e "DEV_USER=${HOST_USER}" \
-e "MEGA_MISSION=${MISSION}" \
-e "TERM=${TERM:-xterm-256color}" \
-e "LANG=en_US.UTF-8" \
--hostname "megaclaude" \
"${IMAGE_NAME}:${IMAGE_TAG}" \
$SHELL_ARG
Worth calling out:
-v "${PROJECT_DIR}:/workspace"binds your project in. Changes Claude makes are real and persist after the container exits. This is the one place where the sandbox touches your host.-v "${HOME_VOLUME}:/home/${HOST_USER}"is the persistent volume for Claude, mise, and auth.--rmmakes the container disposable. It cleans up after itself.- No git credentials. Your
~/.gitconfig,~/.ssh, and any credential helpers stay on the host. Claude can use git inside the container (for diffing, branching, etc.), but it can’t push anywhere. All commit and no push makes Claude a safe boy.
Usage #
It’s straightforward:
# Launch Claude in a project
megaclaude ./my-project
# Full rebuild (nuke cached tools)
megaclaude ./my-project --rebuild
# Drop into a shell to poke around
megaclaude ./my-project --shell
A typical workflow looks like:
megaclaude ./my-project- Type in a mission: “Add unit tests for the authentication module”
- Walk away
- Come back, review the changes with
git diff - Commit what you like, discard what you don’t
The Trade-offs #
I’m not going to pretend this is perfect.
What it gives you:
- Claude Code with zero permission friction
- A sandbox that limits blast radius to your project directory
- Persistent tooling across sessions (no re-installing every time)
- Automatic project toolchain setup via mise
- A mission-based workflow where you describe what you want and let Claude execute
What it doesn’t give you:
- Network isolation. The container has full internet access. Claude needs this to search docs and install packages, so the sandbox isn’t hermetically sealed. It’s a screen door, not a vault.
- Git push protection if you break it. Claude can’t push (no credentials), but if you were to mount your SSH keys or gitconfig, that protection disappears. Don’t do that. Resist the temptation.
- Infinite trust. Claude is autonomous inside the container, but it’s still an LLM. Review the output. Use
git diff. Trust but verify. Mostly verify.
Why Not Just Use --dangerously-skip-permissions Directly? #
You could! But then Claude has access to your entire machine. It can read your SSH keys, your environment variables, your other projects, your credentials. The Docker sandbox keeps the worst case to “it makes a mess of one project directory.” That’s a much smaller kaboom.
It’s the difference between giving someone the keys to one room versus the whole building. And in this room, the walls are padded and the windows don’t open.
Wrapping Up #
megaclaude has become my go-to for “fire and forget” Claude Code sessions. Type a mission, go make lunch, come back to a completed task (or at least a solid first pass). It’s not replacing my normal interactive Claude Code sessions, those are great when I want to be hands-on, but for autonomous work the sandboxed approach just makes sense.
The whole thing is ~420 lines of bash. No external dependencies beyond Docker. It builds its own image, manages its own volumes, and gets out of your way. The full script is here if you want to grab it: