Running Claude Code Without Guardrails — While Keeping It Safe
Using a devcontainer to give Claude the freedom to actually get things done
I've been experimenting with agentic development since 2024 and I'm starting to document what I've learned, particularly around building BattleTabs. This is part of an ongoing series about building BattleTabs with Claude Code. Subscribe to receive future posts in your inbox.
When I first started using Claude Code for BattleTabs, I was running it directly on my machine. It worked, but every session started with the same friction — making sure the right Node version was active, that Docker was running, that the database was up. And every time Claude needed to run a command, I’d get a permission prompt. Install a dependency? Approve. Run the test suite? Approve. Generate Prisma types? Approve. It adds up fast.
So I moved everything into a devcontainer — think of it like running a separate virtual PC inside your computer, isolated from the rest of your machine, pre-loaded with exactly the tools the project needs. And because the container is isolated, I can let Claude run freely inside it. No permission prompts. It can install packages, run builds, execute tests, reset the database — whatever it needs to do — and there’s zero risk to my host machine. The container is the sandbox.
Instead of babysitting every command, I give Claude a task and walk away. It figures out what to run, runs it, hits an error, fixes it, tries again — all without stopping to ask me. That autonomy is what makes the agent workflow actually work.
To make this seamless, I wrote a launcher script — ./dev. It checks that Docker and the databases are running, builds the container image if the Dockerfile changed, and launches Claude Code with --dangerously-skip-permissions inside it. One command to go from “I want to start working” to “Claude is ready.”
The container persists between sessions. Run ./dev once, and it creates the container with the repo mounted, environment variables set, ports forwarded. Run ./dev again in another terminal, and it execs into the same running container — instant startup. I can have four Claude instances running in parallel, all sharing the same container, the same node_modules, the same database.
./dev # Start Claude Code (creates or reuses container)
./dev # Second instance — execs into same container, instant
./dev rm # Fresh start when needed
./dev status # How many instances are running?The Dockerfile itself installs Claude Code directly into the image, so there’s no “install Claude” step at startup. It also includes Chromium and a virtual display server for automated browser testing — I can have Claude take screenshots of the running game inside the container without a GPU or a real display. But I’ll cover that workflow in a later post.
This also means I don’t have to repeat setup when I spin up parallel agents. Every agent inherits the same ready-to-go environment. No redundant installs, no version mismatches. One container definition, consistent every time.
The next unlock was combining this with git worktrees (think of each worktree as a separate copy of the project folder, like duplicating your Unity project to try something experimental.)
BattleTabs is a monorepo (a single massive codebase containing all related apps), and I often want Claude working on multiple things at once — say, a backend fix and an unrelated UI change. Git worktrees let me have multiple checkouts of the same repo inside the same container. Each worktree is an independent working copy with its own branch, so I can have separate Claude sessions running in parallel without them stepping on each other’s changes.
/workspaces/battletabs # main checkout
/workspaces/battletabs-feature-a # worktree for feature A
/workspaces/battletabs-feature-b # worktree for feature BEach session gets its own space to work in. They can build and test independently. This becomes really important when I start using agent teams.
One more thing I didn’t think about initially: how do you share files with Claude when it’s running inside a container? You can’t just copy-paste a screenshot into a terminal in a devcontainer. Opening VS Code attached to the container and navigating to the right directory works, but it’s slow and clunky.
The trick I landed on is dead simple: run npx serve on a folder on your host machine — say, your Downloads folder — and it starts a local file server. From inside the container, Claude can access those files at http://host.docker.internal:3001/. I drop a screenshot into that folder, give Claude the URL, and it can see exactly what I see. No more trying to describe “the button is slightly off to the left” — I just show it.
# On your host machine
npx serve ~/Downloads -p 3001
# Claude (inside the container) can now fetch:
# http://host.docker.internal:3001/screenshot.pngOne gotcha: Claude can’t always read from host.docker.internal URLs directly — sometimes its built-in web tools don’t resolve the hostname. But curl inside the container always works, so if Claude needs to grab a file, it can just curl it to disk and read it from there. Minor thing, but worth knowing so you don’t waste time debugging it.
BattleTabs runs on a fixed local port inside the container, so I can access the full game in my browser while Claude is making changes. Hot reload means I see changes almost instantly. This tight loop — Claude codes, I test, I screenshot what I see, Claude fetches it and adjusts — is really the core of how I work now.
Getting the devcontainer right took some effort upfront, but it's now an essential part of how I start every new project.



