- Shell 59%
- Dockerfile 41%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
|
|
||
| .gitea/workflows | ||
| .env.dev.example | ||
| .gitignore | ||
| docker-compose.yml | ||
| docker-entrypoint.sh | ||
| Dockerfile | ||
| README.md | ||
| run-repo.sh | ||
| run.sh | ||
| setup-claude.sh | ||
claude-devcontainer
A Docker image for running Claude Code in an
isolated, disposable container. It ships Node 22, the .NET 10 SDK,
dotnet-ef, the GitHub CLI, postgresql-client, and Playwright with
Chromium/Firefox pre-installed — a non-root dev user runs everything so
claude --dangerously-skip-permissions is safe to use.
The image is built and pushed to git.butzei.de/robert/claude-devcontainer
on every push to master (see .gitea/workflows/build.yml).
Prerequisites
- Docker (rootful or rootless)
~/.claudeand~/.claude.json— your existing Claude Code login/config on the host (bind-mounted in, so auth carries over)~/.git-credentials— for git push access from inside the container (git config --global credential.helper storeon the host populates this)~/.config/gh— forghCLI auth, if you use it
Setup
Copy the example env file and adjust it for your setup:
cp .env.dev.example .env.dev
Key variables (see comments in .env.dev.example for details):
DOCKER_SOCKET— path to the Docker socket, for Testcontainers / Docker-in-Docker. Defaults to/var/run/docker.sock; rootless Docker users should point this at/run/user/<uid>/docker.sock.DOCKER_GID— GID of the hostdockergroup, only needed on standard (non-rootless) Docker so the container'sdevuser can reach the socket.DEV_UID/DEV_GID— override if your host user isn't UID/GID 1000.ANTHROPIC_API_KEY— optional; leave unset to use the OAuth credentials copied in from~/.claude.GIT_TOKEN— a Gitea personal access token, written to~/.git-credentialsinside the container at startup.
.env.dev is gitignored — never commit real credentials.
Usage
There are two ways to run the container: a plain docker run for local
projects, and a docker-compose.yml profile for headless repo mode.
Mode 1: Mount a local project (run.sh)
Runs docker run directly, mounting a project directory from the host into
the container at its real path (so Claude's per-project session history
matches between host and container) and starting Claude immediately with
--dangerously-skip-permissions. The container is started with --rm, so
it's removed automatically when Claude exits.
./run.sh # use the current directory as the project
./run.sh /path/to/project # use a specific directory
Mode 2: Clone a repo and run headless (run-repo.sh)
Clones a git repository inside the container and starts Claude with
--dangerously-skip-permissions right away — intended for driving the
container remotely via claude.ai/code.
./run-repo.sh <repo-url> [branch]
Watch the clone and Claude startup:
docker compose -f docker-compose.yml --profile repo logs -f
First-time setup only — link the container to claude.ai/code:
docker compose -f docker-compose.yml --profile repo exec claude-repo bash
# inside the container:
claude
/web-setup
The link is saved to ~/.claude and persists across restarts. Requires
gh auth login on the host beforehand, and git credentials in
~/.git-credentials for private repos.
Stop it with:
docker compose -f docker-compose.yml --profile repo down
How it works
docker-entrypoint.shruns as thedevuser on container start. It callssetup-claude.shvia a scoped, passwordlesssudorule to fix permissions on the bind-mounted~/.claude,~/.claude.json,~/.git-credentials,~/.config/gh, and the working directory — this is needed because rootless Docker bind mounts can appear asroot:rootinside the container. Thedevuser has no other root access.- In repo mode, the entrypoint then clones
REPO_URL(optionally atBRANCH) into~/<repo-name>and execsclaude --dangerously-skip-permissions. - In local mode (
run.sh), the entrypoint execs whatever commanddocker runwas given —claude --dangerously-skip-permissions.