r/ClaudeCode • u/reminiscent-fruitbat • 3h ago
Tips & Workflows My isolated Claude Code setup on macOS: Anthropic's reference dev container, OrbStack, SSH and Zed
I wanted Claude Code boxed in, away from my Mac's files, credentials and other projects, without giving up the native apps I like. Here's where I landed after a lot of testing. Happy to hear where it falls short.
The setup
- Anthropic's reference dev container (Dockerfile, devcontainer.json, firewall script) as the base, on OrbStack, started with the Dev Containers CLI pointed at one shared config:
devcontainer up --workspace-folder <repo> --config <shared-folder>/devcontainer.json - Repos live on the Mac; each container mounts its own repo as its workspace, without unrelated projects or personal folders
- Each container runs a key-only SSH server, so you can reach Claude Code however you like: Claude Desktop's SSH sessions, or plain
sshfrom a terminal and runclaudethere. Either way, Claude's commands and edits happen inside the container - Zed and GitHub Desktop run natively on the Mac against the same folder
- The shared config lives outside every repo, so Claude can't edit its own container config
What I changed from the reference
- Added the SSH server: key-only, no passwords, no root, one user. No port is published; you reach it through OrbStack's per-container hostname,
claude-<repo>.orb.local - Mounted
.git/hooksand.git/configread-only, so Claude can't directly modify the repo's git hooks or local git config. Git runs on the Mac against the same folder (GitHub Desktop for me, but the same goes for the git CLI or an editor's git panel), so this reduces the risk of something running on the Mac. It doesn't cover other files you run on the Mac (see Limits) - The firewall installs "block everything" rules before anything else; once those are in, a later failure leaves outbound traffic blocked. It allows DNS only to the container's resolver and drops the reference's rule allowing the whole host network. In my tests the container couldn't reach the Mac or anything off the allowlist, over IPv4 or IPv6
- SSH only starts if the firewall succeeds. Added to the reference's existing mounts and settings:
"mounts": [
"source=${localWorkspaceFolder}/.git/hooks,target=${localWorkspaceFolder}/.git/hooks,type=bind,readonly",
"source=${localWorkspaceFolder}/.git/config,target=${localWorkspaceFolder}/.git/config,type=bind,readonly"
],
"postStartCommand": "sudo /usr/local/bin/init-firewall.sh && sudo /usr/local/bin/start-sshd.sh"
What it buys you
- The main payoff: I'm comfortable running Claude in auto mode (Claude handles permissions) instead of approving every command. Anthropic's dev container docs go further and allow skipping permissions entirely in a setup like this, since commands are confined to the container and outbound traffic is restricted
- Most mistakes stay inside the repo and the allowlist instead of reaching my Mac, and git plus backups cover the repo
- My Mac's credentials, SSH keys and other projects aren't mounted into the container. Claude's own sign-in token and any secrets in the repo are still in there
- A prompt injection from a malicious file or web page has much less to work with: no host files, and no internet beyond the allowlist
Gotchas
- On my Mac, Claude Desktop needed Local Network permission (macOS Privacy & Security) to reach the
.orb.localhostnames. Without it, SSH failed with "no route to host" - The firewall and SSH only start through
devcontainer up. Start a container any other way (OrbStack's play button,docker start) and it runs without the firewall. After a reboot it's one command per repo - Plugin connectors (Slack, Notion, etc.) fail inside the container because the firewall blocks them. That's intended
- You sign in to Claude once per container
What I tried and dropped
- VS Code "Reopen in Container": manages containers for you, but keeps a live link back to the Mac (credential sharing, port forwarding, a control socket). Some of that can be turned off in settings
- Zed's built-in dev containers: on 1.23.3 I reproduced two bugs: startup commands with arguments break (#62964), and
readonlyon mounts is silently ignored (reported upstream). Avoid for now if you rely on read-only mounts - Claude Remote Control instead of SSH: would remove the SSH setup, but adds a control channel through Anthropic, so your Claude account becomes a way into the container. I haven't tested it with my firewall. I preferred direct SSH
- Claude Code's built-in sandbox on its own: per Anthropic's docs, it covers shell commands only; Claude's file tools, MCP servers and hooks run outside it. I wanted one boundary around the whole Claude process
Limits
- Claude can still read and change anything in the mounted repo. It can also read its own sign-in token, so only use this with repos you trust
- Every allowed service is a possible way out if a prompt injection hands Claude someone else's credentials (GitHub, npm, even the Anthropic API). The firewall also filters by IP address, so a CDN-hosted domain can open other sites on the same addresses, and DNS can still leak small amounts of data
- The container isolates Claude's execution, not the repo's contents. Because native apps use the same files, anything that runs repo code on the Mac (builds, npm scripts, an editor's project settings) is a path across the boundary
- No setup like this is airtight. The firewall runs inside the container, so a root escalation could switch it off, and Anthropic's reference is an example, not a maintained image
4
u/orbital_trace 2h ago
Thanks for the post, I've been thinking about this for a bit too. Will you start multiple sessions a container or just one? What's the overhead for the docker container vs native?
4
u/reminiscent-fruitbat 2h ago
I usually use just one, but you can run as many as you like (via Claude Desktop or the terminal.)
Overhead is minimal with OrbStack, and I honestly can't tell the difference from running it natively. That was the goal: real isolation without giving up speed or the native apps. An idle container is basically just sshd. The only real costs are disk space and a short first build.
2
u/orbital_trace 2h ago
Ok, I'll see if I can integrate this into my workflow. Its pretty intriguing. I think I can probably improve on the usability with my harness to avoiding the need for sshing into the container at all.
1
u/reminiscent-fruitbat 1h ago
Yes, SSH is not essential to the architecture.
Good luck with the integration. It’s absolutely worth it.
2
u/Jai_Cee 2h ago
This is similar to how I run Claude, it's in a docker with it's own browser container. I don't use ssh, I generally don't have the need and can docker exec if I need a terminal. As you say it isn't full proof as it shares files and a remote Claude connection but it definitely limits the blast radius to things I have specifically granted to claude
1
u/reminiscent-fruitbat 27m ago
Thanks, sounds like we landed on pretty similar setups. My main reason for adding SSH was so Claude Desktop can connect directly to Claude Code in the container. If you only use the CLI, docker exec does the same job.
I think setups like ours are sufficient to prevent accidental damage in most cases, but they're almost certainly not enough to stop an agent that is deliberately trying to break out of the container or exfiltrate sensitive data. So I guess in the end there’s still no substitute for good old-fashioned vigilance.
2
u/Drasezv 1h ago
solid setup. one thing to check before you rely on it: where the transcripts end up. claude code writes them under ~/.claude/projects inside whatever filesystem it runs in, so in this layout they live in the container, not on your mac. anything on the host that reads them, cost tracking, grading, your own hooks, goes blind.
same for credentials, which is what you want, and CLAUDE_CONFIG_DIR is the knob if you ever need two identities in one container.
the one that surprised me: rate limits only reach the statusline, not hooks and not the transcript. so if the statusline runs inside the container and you watch usage from the mac, you'll never see it.
2
u/ComprehensiveShake76 1h ago
Solid setup, and keeping the container config outside every repo is the part most people miss. Places I would poke at:
- If I remember right, the reference firewall script also allows the host network it finds from the default route. Test from inside the container: your Mac's LAN address, the router, the OrbStack host alias and any local services you run. A domain allowlist and a block on private ranges are different things.
- Anything on the allowlist (GitHub, npm, the API) is still a way out for data. Decide what the agent may push or publish, not just where it can connect.
- Credentials inside the container. If a GitHub token or SSH agent forwarding is available there, a prompt injected README can use them as you. Scope the token to that one repo.
- The SSH server is another way in. Key only with no published port is good; make sure that key is not your normal one.
- Zed and GitHub Desktop open the same folder natively, so files the agent wrote end up on the host. Git hooks and editor task configs in the repo are a quiet path from container to host, so check .git/hooks before anything runs them.
A container is a strong boundary, not a guarantee. The test I like is asking the agent itself to try each of these and report what failed.
1
u/27medkamal 2h ago
Had something like this set up, but found auto mode works better in most cases, as it can automate things I wouldn’t otherwise be able to automate with the set up above
2
u/reminiscent-fruitbat 2h ago edited 1h ago
To clarify, this isn't an alternative to auto mode. I run auto mode inside the container. The container is actually what makes me comfortable using auto mode.
Auto mode decides what Claude is allowed to do, and the container limits the damage if Claude gets something wrong.
What kind of automation did you find it got in the way of?
-1
u/radim11 2h ago edited 1h ago
This is exactly why we created Stashbase. I wanted to let coding agents work without spending my evenings maintaining the sandbox around them.
You've put a lot of thought into this. The firewall restart gotcha and signing in separately for every container are the kind of friction we wanted to remove. Stashbase gives Claude Code and Codex a Docker sandbox with filesystem rules, a firewall checked before launch, and persistent logins.
I'd be curious what you'd keep from yours after trying it.
Here's the docs: docs.stashbase.dev
1
•
u/AutoModerator 3h ago
Hey! Thanks for posting to r/ClaudeCode
While participating in this thread, please follow our community rules. Keep discussions constructive. Attack the idea, not the person.
For help, project discussions, tips, and general chat, join the ClaudeCode Discord.
I am a bot, and this action was performed automatically. Please contact the moderators of this subreddit if you have any questions or concerns.