Every single action. Every file read, every edit, every shell command. Claude Code asked for permission, and I had to say yes. Or no. Or stare at the prompt for a moment, context-switch back to what it was doing, parse whether the action made sense, approve it, then wait another 40 seconds for the next one.
Forty seconds is an awkward gap. Too short to do anything meaningful. Too long to just sit there. So you start opening other terminals, running parallel sessions, checking email, and before you know it you’re juggling three half-baked contexts and none of them are moving forward. Multitasking is a pipe dream. Nobody actually does it well. It just destroys your focus while giving you the illusion of progress.
I hated the process. Genuinely hated it. I’d heard about --dangerously-skip-permissions, the mode where Claude Code just does its thing without asking. But running that on my actual machine? No chance. I wasn’t even worried about the sophisticated attack vectors, prompt injection from a web page or whatever. It’s much simpler than that. The LLM makes a wrong decision and wipes something out. That’s it. That’s the risk I wasn’t willing to take on bare metal.
So I was stuck. The tool was powerful but the workflow was miserable.
This is a pattern I’ve seen a hundred times in my career: you know the right move is to stop and fix something foundational, but the work you’re doing right now feels like progress. You’re building features, shipping things, ticking boxes. Stopping to fix infrastructure feels like going backwards.
Containerisation was my technical debt. I knew it would solve the problem. I’d done research, looked at options, had a rough plan. But I kept putting it off because every day there was something more immediately productive to do. This was step three in what I’ve been calling my autonomous code journey, after memory and deep research. I could have argued that containers should have come first, since they would have made everything else faster. But doing things manually first, automating afterwards, is a principle I believe in. If I’d started with skip-permissions from day one, I think I would have missed important lessons about how Claude Code actually works.
Containerisation was only possible because Claude Code is a CLI. A CLI is just a process. You can pipe it, loop it, script it, and shove it inside a container. I grew up on Unix systems. Small utilities you can compose together is a core philosophy for me. So the path was immediately clear: build a container image with the tool installed, mount the project directory, drop all privileges, run it in skip-permissions mode. The worst that can happen is it trashes the repository files. And those are pushed to remote and backed up. I can live with that.
I looked at several approaches: Docker’s official sandboxes, third-party wrappers, DevContainers, Claude Code’s built-in OS-level sandboxing. I went with a custom Docker setup because I wanted full control and transparency. I need to see exactly what’s mounted, what’s dropped, and what’s excluded. Prior experience with Docker Desktop on Mac was slow and clunky, so I switched to OrbStack. Same Docker CLI, different implementation, lightning fast. That alone removed a lot of friction.
Getting Claude Code running in a container takes about 20 minutes. Getting it running smoothly took weeks of refinement. The basic docker run is the easy part. Everything after that is where it gets interesting.
Path consistency was the first thing. I wanted the file paths inside the container to match the host exactly. When Claude Code creates a session, references files, or stores metadata, it uses absolute paths. If those paths are different inside the container, everything breaks: session history, tool references, project detection. So I mount the workspace at the same path. /Users/spike/Documents/knowhere inside the container is the same as on my Mac. It sounds trivial but its impact is not.
The shared venv problem came next. I run Python projects with virtual environments. Every container installing packages from scratch means minutes of overhead each time, plus disk space. And packages installed on Mac won’t work inside the container anyway due to the different architecture. My solution was a named Docker volume for the venv, mounted at the same path. The container’s entrypoint checks whether the host’s packages have changed (via a pip freeze hash), and only re-syncs when something is different.
But the real rabbit hole was authentication.
On a Mac, Claude Code stores OAuth tokens in the macOS Keychain. It creates a credentials.json file, imports the token into the Keychain, then deletes the file. On Linux, it just keeps the file. When you run Claude Code inside a Linux container with the config directory mounted from your Mac, both systems fight over that file. The container writes its token to credentials.json. The Mac host sees a new file, imports it into the Keychain, and deletes it. The container loses its credentials. The host now has the container’s token, which may already be stale. Everything gets confused, and you’re constantly getting logged out.
I first tried to solve this by generating a long-lived setup token, one that lasts a year, and injecting it as an environment variable. Claude Code has this option, and it worked. But I never liked it. The whole point of short-lived tokens is that if one gets exfiltrated, the damage window is small. A year-long token sitting in an environment variable defeats that. So I went back to the root cause.
The fix turned out to be straightforward once I understood the mechanics. I bind-mount a dedicated credentials file from a host path into the container. The containers update their file. The Mac host never sees it because it’s not in the directory the Keychain watcher monitors. Each system stays in its lane. All containers share the same credentials file, so logging in once works everywhere.
I’m not pretending this setup is airtight. It’s not. Here’s what it actually provides: the container runs as a non-root user with matching UID/GID. All capabilities are dropped (--cap-drop=ALL). No privilege escalation is possible (--security-opt=no-new-privileges). The container can’t see ~/.ssh, ~/.aws, or any credentials beyond what’s explicitly mounted. The blast radius is the project files, which are version-controlled.
Here’s what it doesn’t protect against: a prompt injection in a web page could, in theory, cause the LLM to execute something inside the container. The container has internet access because my workflow requires it. I run deep research, web searches, and API calls from inside containers. Filtering egress to known hosts would completely defeat the point. So I accept that risk.
I also share context in my memory files, so in theory data could be exfiltrated. But the OAuth token refreshes every eight hours, and the container is destroyed after each session. An attacker would need to install a hook that reinitialises itself on the next session start by dropping a script into the repository. I review my commits, and I have tooling that surfaces changes.
It’s not perfect. It’s good enough. And it’s a meaningful step up from running skip-permissions on bare metal, which was the real alternative.
The end result is a shell alias. I type one command and a container spins up, syncs packages if needed, and drops me into Claude Code with full autonomy. No permission prompts. No context switching. Zero friction. And most importantly, no 40-second gaps and allow-fatigue.
The container itself is boring infrastructure. The interesting part is what it unlocked. When you stop fighting the tool, when you stop interrupting every three seconds to approve a file read, the nature of the work changes. You can describe what you want, walk away, come back, and review the result. You can run multiple sessions in parallel on different tasks, each in its own isolated container, without any of them stepping on each other or on your host.
The irony is that I resisted this for weeks because it felt like I was stopping productive work to fix plumbing. But the plumbing was the bottleneck. Every minute I spent approving routine actions was a minute I wasn’t thinking about what I was actually trying to build.
If you’re running Claude Code on bare metal and hating the permission prompts, try containerising it. It doesn’t have to be perfect. Anything beats skipping permissions on the host or mindlessly hitting allow.
No posts

Comments
Nothing yet. Say the first thing.
Sign in to join the conversation.