# 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 ```bash # 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 ```bash 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 [claude-args...]` | Launch Claude for the project here. Anything extra is passed straight to `claude`. | | `safeclaude build` | Rebuild the project's container from scratch. | | `safeclaude init` | Create a starter `.safeclaude/` in the current directory. | | `safeclaude envs` | List the containers and stored data safeclaude has created. | | `safeclaude version` | Print the safeclaude version. | safeclaude always works on the project you're in — the current directory, or the nearest parent that has a `.safeclaude/` folder, so launching from a subdirectory still finds 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 cache/ # scratch space on the host, gitignored (dependencies, downloads…) .env # secrets, kept out of git (.env.example shows the format) version # the safeclaude version this config was created with ``` A few things worth knowing: - **Two places setup can live, and the difference matters.** Slow, one-time installs (system packages, a pinned language version) 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 a `hooks/` 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, so a launch with nothing to do is near-instant. - **`cache/` is your scratch space.** It lives on the host and is gitignored, so it survives rebuilds and `docker volume` resets without ending up in your repo — a good home for installed dependencies, downloads, or "already did this" markers. See [`example/`](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.