Baseline devcontainer configs for Claude Code. Adds a secure, firewall-restricted dev environment to any repo in one command.
Requires
jq— used by the installer and updater to manage extensions.
Interactive — prompts for template, target directory, and VS Code extensions:
bash <(curl -fsSL "https://raw.githubusercontent.com/zizzfizzix/claude-devcontainers/main/install.sh")
install.shdownloads only the files it needs — no clone required.
Non-interactive — pass the template (and optionally a target directory) as arguments:
curl -fsSL "https://raw.githubusercontent.com/zizzfizzix/claude-devcontainers/main/install.sh" | bash -s -- typescript
curl -fsSL "https://raw.githubusercontent.com/zizzfizzix/claude-devcontainers/main/install.sh" | bash -s -- typescript /path/to/projectThen open the folder in VS Code → Reopen in Container.
The installer works in Git Bash (ships with Git for Windows). Use the non-interactive form (curl | bash -s -- typescript) — bash <(...) process substitution is not supported in Git Bash.
Run update.sh from inside the container (or directly on the host):
.devcontainer/update.shThe updater fetches the latest upstream version of itself first, then syncs all upstream-owned files while leaving your project-local customizations untouched. It prints a GitHub compare URL so you can review what changed.
| Template | Base image | Extra tooling |
|---|---|---|
typescript |
mcr.microsoft.com/devcontainers/base:debian |
Node 24, git-delta, Claude Code |
php |
mcr.microsoft.com/devcontainers/base:debian |
PHP 8.2 + extensions, Composer, WP-CLI, Node 24 |
research |
mcr.microsoft.com/devcontainers/base:debian |
Pandoc, ripgrep, Markdown tools — firewall disabled (UNRESTRICTED_NETWORK=true) |
All templates include:
- Claude Code with
--dangerously-skip-permissionsaliased (safe inside the firewall-restricted container) iptablesfirewall: allowlists only necessary outbound domains, drops everything else- Transparent HTTPS proxy (mitmproxy) with OAuth credential management
- Persistent shell history and Claude config across container rebuilds
- git-delta, fzf, zsh
After the container starts, authenticate Claude Code:
claude /loginCredentials are persisted in .devcontainer/.data/proxy/credentials.json and survive container rebuilds.
.devcontainer/
├── proxy/
│ ├── Dockerfile # mitmproxy sidecar image
│ ├── addon.py # token swap + request inspection
│ └── start.sh # firewall setup + transparent proxy launch
├── claude-wt.zsh # git worktree helper for multi-branch Claude sessions
├── devcontainer.json
├── docker-compose.yml
├── docker-compose.override.yml # project-local Docker Compose overrides (yours to edit)
├── extensions.json # upstream extension catalog
├── extensions.local.json # your extension selections (generated on install)
├── postcreate.sh
├── postcreate.local.sh # project-local post-create hook (yours to edit)
├── poststart.sh
├── poststart.local.sh # project-local post-start hook (yours to edit)
├── shell-config.zsh
├── shell-config.local.zsh # project-local shell config (yours to edit)
├── update.sh # upstream update script
└── .upstream-version # version stamp, e.g. typescript@v1.2.0
Files marked "yours to edit" are installed once and never overwritten by update.sh.
update.sh distinguishes between upstream-owned files (always synced) and project-local files (installed once, never touched again). Customize via the local files:
| What you want to customize | File to edit |
|---|---|
| Extra packages, repo setup | postcreate.local.sh |
| Per-session startup logic | poststart.local.sh |
| Shell aliases, exports | shell-config.local.zsh |
| Extra Docker services | docker-compose.override.yml |
| VS Code extension selection | extensions.local.json |
extensions.json is the upstream extension catalog. Extensions have two tiers:
- base — always installed (Claude Code, GitLens)
- optional — shown in the install prompt; your choices are saved to
extensions.local.json
update.sh re-reads extensions.local.json on every upgrade so your selections survive.
The claude-proxy sidecar runs mitmproxy as a transparent HTTPS proxy alongside an iptables/nftables firewall. All outbound traffic from the dev container is redirected through it.
- Domain allowlist: only requests to whitelisted domains (GitHub, npm, Anthropic, VS Code, etc.) are forwarded; all others get a 403.
- Credential management: real OAuth tokens are captured from login responses, stored in
.devcontainer/.data/proxy/credentials.json, and replaced with dummy tokens that are swapped back transparently on every outbound request. This prevents real tokens from appearing in Claude's context or logs. - CA certificate: the proxy CA is automatically trusted in the system store and Python's
certifibundle on container start.
Add extra domains via the claude-proxy service environment — no script fork needed:
claude-proxy:
environment:
EXTRA_ALLOWED_DOMAINS: "registry.example.com cdn.example.com"The proxy resolves and allowlists them at container start.
To allow all outbound traffic (e.g. for initial setup or debugging):
claude-proxy:
environment:
UNRESTRICTED_NETWORK: "true"Host credentials are forwarded into the container before it starts via initializeCommand scripts. Each integration reads env vars from the host, writes them to .devcontainer/.data/, and the container loads them at startup.
If gh is installed and authenticated on the host, the token is read automatically via gh auth token and forwarded into the container — no manual setup required.
Set the following env vars on the host before opening in container:
| Variable | Description |
|---|---|
ATLASSIAN_API_TOKEN |
Atlassian API token |
ATLASSIAN_EMAIL |
Account email |
ATLASSIAN_SITE |
Site name (e.g. myorg.atlassian.net) |
postcreate.sh will authenticate acli for both Jira and Confluence automatically. If the vars are not set, the step is silently skipped.
Set GITLAB_TOKEN on the host before opening in container. For self-managed or GitLab Dedicated instances, also set GITLAB_HOST:
| Variable | Description |
|---|---|
GITLAB_TOKEN |
GitLab personal access token |
GITLAB_HOST |
GitLab instance URL (optional, defaults to https://gitlab.com) |
glab reads GITLAB_TOKEN directly from the environment — no explicit login step is needed. If the var is not set, the step is silently skipped.
claude-wt.zsh provides a claude-wt <branch> function that creates a git worktree for <branch>, injects a one-shot VS Code task to launch Claude Code, and reopens VS Code into that worktree. Useful for running multiple Claude sessions on different branches simultaneously.
Interactive:
./install.shNon-interactive:
./install.sh [typescript|php|research] [target-directory]target-directory defaults to the current directory.
├── base/
│ ├── claude/
│ │ └── settings.json
│ ├── stubs/ # init-only stub files installed into projects
│ ├── claude-wt.zsh
│ ├── extensions.json # VS Code extension catalog
│ ├── postcreate.sh
│ ├── poststart.sh
│ ├── shell-config.zsh
│ └── update.sh # upstream update script
├── proxy/
│ ├── addon.py
│ ├── Dockerfile
│ └── start.sh
├── templates/
│ ├── php/
│ │ ├── devcontainer.json
│ │ ├── docker-compose.yml
│ │ ├── manifest.txt
│ │ ├── postcreate-php.sh
│ │ └── version.txt
│ ├── research/
│ │ ├── devcontainer.json
│ │ ├── docker-compose.yml
│ │ ├── manifest.txt
│ │ └── version.txt
│ └── typescript/
│ ├── devcontainer.json
│ ├── docker-compose.yml
│ ├── manifest.txt
│ └── version.txt
├── .devcontainer/ # this repo's own devcontainer
│ └── devcontainer.json
├── .release-please-manifest.json
├── release-please-config.json
└── install.sh
- Add
templates/<name>/devcontainer.json,docker-compose.yml,manifest.txt, andversion.txt(start at0.0.0) - Add
<name>to theTEMPLATESandDESCRIPTIONSarrays ininstall.sh - Add a package entry to
release-please-config.jsonand.release-please-manifest.json - Add a row to the Templates table above