safeclaude
Run Claude Code inside a locked-down container, one per project. Your code is shared with the container so Claude can edit it, but everything Claude runs stays boxed in — so a bad command can't touch the rest of your machine.
It works like a language version manager (think rvm/nvm/pyenv): this repo
holds only the shared plumbing, and each project keeps its own setup in a
small .safeclaude/ folder. When you launch, safeclaude reads that folder,
builds the container once, and reuses it after that.
Install
# Put the launcher on your PATH (any dir on your PATH works)
ln -s "$(readlink -f ./safeclaude)" ~/.local/bin/safeclaude
That's it — the first launch builds what it needs automatically.
Use
cd ~/my-project
safeclaude init # create a .safeclaude/ folder, then edit it for your project
safeclaude # launch Claude in this project's container
| Command | What it does |
|---|---|
safeclaude [PATH] [claude-args...] |
Launch Claude for a project (default: current dir). Anything extra is passed straight to claude. |
safeclaude build [PATH] |
Rebuild the project's container from scratch. |
safeclaude init [PATH] |
Create a starter .safeclaude/ folder. |
safeclaude envs |
List the containers and stored data safeclaude has created. |
A "project" is any folder (or a parent of it) that has a .safeclaude/ folder,
so you can launch from a subdirectory and it'll still find the right one.
What goes in .safeclaude/
.safeclaude/
Dockerfile # which system packages / language versions this project needs
hooks/ # setup scripts that run each time the container starts
.env # secrets, kept out of git (.env.example shows the format)
A few things worth knowing:
- Two places setup can live, and the difference matters. Slow, one-time
installs (system packages, a language toolchain) go in the
Dockerfile— these get cached, so they don't repeat. Anything that needs your actual code present, or that should persist between runs (installing dependencies, starting a database proxy), goes in ahooks/script that runs at launch. - It only rebuilds when something changed. safeclaude remembers what it already built, so a normal launch starts right up with no waiting.
- Hooks are safe to run every time. They check before doing work — e.g. the starter dependency hook only reinstalls when your lockfile actually changed.
See example/ for a real, filled-in Ruby + Postgres setup you can
copy from.
Security
These are applied every launch, by safeclaude itself — a project's own config can't loosen them:
- Claude runs as a normal user, not root, so it can't make system-wide changes.
- The container is stripped of special privileges it doesn't need.
- It can't gain new privileges partway through.
- Each project gets its own private storage, so one project can't see or break another's setup.
Network access is otherwise normal. If you want to cut it off, that's a small change in the launcher.