Context
Comparing the claude-code/ template against a consumer project (utili) that uses these images. The README is thorough, but the template files themselves have gaps that make adoption harder than it should be.
Missing template files
1. No .claude/settings.json template
Consumer projects need a starting settings.json with common permissions. Without one, every adopter reinvents this from scratch:
{
"permissions": {
"allow": [
"Bash(bun:*)",
"Bash(wrangler:*)",
"Bash(git:*)",
"Bash(curl:*)",
"Bash(mkdir:*)",
"Bash(ls:*)"
]
}
}
Should be in claude-code/.claude/settings.json as a starting point.
2. init-plugins.sh not in claude-code/ directory
The README references it as optional and links to the repo's own .devcontainer/init-plugins.sh, but adopters have to navigate to a different directory to find the template. Should be alongside the other template files in claude-code/.devcontainer/init-plugins.sh.
3. No Codex CLI integration
Some consumers use both Claude Code and Codex. This project adds Codex support via:
Worth documenting as an optional pattern in the README or including in the template with a comment.
Template improvements
4. Node_modules mounts are too opinionated
The template devcontainer.json hardcodes services/api, services/app, services/www, packages/shared, packages/database paths. Projects with different structures have to understand and rewrite the entire mounts section.
Consider either:
- A minimal template with just root
node_modules + a comment explaining how to add more
- Or a clearly marked "customize this section" block
5. Missing optional extension suggestions
The template only includes core extensions. Consumer projects commonly add these — worth listing in the README as recommended additions:
| Extension |
Purpose |
streetsidesoftware.code-spell-checker |
Catch typos |
christian-kohler.npm-intellisense |
Autocomplete npm imports |
mattpocock.ts-error-translator |
Human-readable TS errors |
bierner.color-info + kamikillerto.vscode-colorize |
Inline CSS color previews |
drizzle-team.drizzle-orm-snippets |
Drizzle ORM (if using Drizzle) |
ms-playwright.playwright |
Playwright test runner |
6. Default variant in consumer projects missing theme
The template correctly includes Claude Dark theme, but the consumer project's default variant was missing it (only sandbox had it). This suggests the theme was added to the template after the consumer project was set up. Worth noting in the README that existing consumers should sync theme settings:
7. Default and sandbox variants should share Docker volumes
The template uses separate volume prefixes — myproject-* for default and sandbox-* for sandbox. This means node_modules, Claude config, and fish history are duplicated across variants. Installing or updating packages in the default container has no effect in the sandbox (and vice versa), so they drift out of sync.
Both variants serve the same workspace. The sandbox's isolation is network-level (firewall), not package-level. Duplicating volumes adds no security benefit — it just means running bun install twice and wasting disk space.
The template should use the same volume prefix for both variants so they share state:
- "source=sandbox-node-modules-root-${devcontainerId},target=/workspace/node_modules,type=volume",
+ "source=myproject-node-modules-root-${devcontainerId},target=/workspace/node_modules,type=volume",
8. Compose project name collides across consumer projects
The template's docker-compose.yml files don't set a name: field, so Docker Compose derives the project name from the parent directory — claude-sandbox for the sandbox variant. Every consumer project using this template gets the same project name, causing container collisions when switching between projects.
What happens: Opening project A creates container claude-sandbox-devcontainer-1 with bind mounts pointing to project A's paths. Opening project B finds the existing container (same project name), tries to reuse it with --no-recreate, and fails because the bind mounts still point to project A:
error mounting "/host_mnt/Users/.../projectA/.devcontainer/claude-sandbox/init-firewall.sh"
to rootfs at "/usr/local/bin/init-firewall.sh": not a directory
Fix: The template should document that consumers must set a unique name: in their docker-compose.yml:
name: myproject-sandbox # <-- unique per consumer project
services:
devcontainer:
image: ghcr.io/gatezh/devcontainer-images/claude-code-sandbox:latest
pull_policy: always
volumes:
- ../..:/workspace:cached
The Dev Containers CLI reads name from docker compose config output and passes it as --project-name in subsequent commands, so this works without any CLI changes.
Priority
Items 1-2 (missing template files) are the highest friction for new adopters. Items 3-6 are nice-to-haves. Items 7-8 are design issues that cause practical problems for anyone using both variants or multiple consumer projects.
Context
Comparing the
claude-code/template against a consumer project (utili) that uses these images. The README is thorough, but the template files themselves have gaps that make adoption harder than it should be.Missing template files
1. No
.claude/settings.jsontemplateConsumer projects need a starting
settings.jsonwith common permissions. Without one, every adopter reinvents this from scratch:{ "permissions": { "allow": [ "Bash(bun:*)", "Bash(wrangler:*)", "Bash(git:*)", "Bash(curl:*)", "Bash(mkdir:*)", "Bash(ls:*)" ] } }Should be in
claude-code/.claude/settings.jsonas a starting point.2.
init-plugins.shnot inclaude-code/directoryThe README references it as optional and links to the repo's own
.devcontainer/init-plugins.sh, but adopters have to navigate to a different directory to find the template. Should be alongside the other template files inclaude-code/.devcontainer/init-plugins.sh.3. No Codex CLI integration
Some consumers use both Claude Code and Codex. This project adds Codex support via:
Worth documenting as an optional pattern in the README or including in the template with a comment.
Template improvements
4. Node_modules mounts are too opinionated
The template
devcontainer.jsonhardcodesservices/api,services/app,services/www,packages/shared,packages/databasepaths. Projects with different structures have to understand and rewrite the entire mounts section.Consider either:
node_modules+ a comment explaining how to add more5. Missing optional extension suggestions
The template only includes core extensions. Consumer projects commonly add these — worth listing in the README as recommended additions:
streetsidesoftware.code-spell-checkerchristian-kohler.npm-intellisensemattpocock.ts-error-translatorbierner.color-info+kamikillerto.vscode-colorizedrizzle-team.drizzle-orm-snippetsms-playwright.playwright6. Default variant in consumer projects missing theme
The template correctly includes Claude Dark theme, but the consumer project's default variant was missing it (only sandbox had it). This suggests the theme was added to the template after the consumer project was set up. Worth noting in the README that existing consumers should sync theme settings:
7. Default and sandbox variants should share Docker volumes
The template uses separate volume prefixes —
myproject-*for default andsandbox-*for sandbox. This means node_modules, Claude config, and fish history are duplicated across variants. Installing or updating packages in the default container has no effect in the sandbox (and vice versa), so they drift out of sync.Both variants serve the same workspace. The sandbox's isolation is network-level (firewall), not package-level. Duplicating volumes adds no security benefit — it just means running
bun installtwice and wasting disk space.The template should use the same volume prefix for both variants so they share state:
8. Compose project name collides across consumer projects
The template's
docker-compose.ymlfiles don't set aname:field, so Docker Compose derives the project name from the parent directory —claude-sandboxfor the sandbox variant. Every consumer project using this template gets the same project name, causing container collisions when switching between projects.What happens: Opening project A creates container
claude-sandbox-devcontainer-1with bind mounts pointing to project A's paths. Opening project B finds the existing container (same project name), tries to reuse it with--no-recreate, and fails because the bind mounts still point to project A:Fix: The template should document that consumers must set a unique
name:in theirdocker-compose.yml:The Dev Containers CLI reads
namefromdocker compose configoutput and passes it as--project-namein subsequent commands, so this works without any CLI changes.Priority
Items 1-2 (missing template files) are the highest friction for new adopters. Items 3-6 are nice-to-haves. Items 7-8 are design issues that cause practical problems for anyone using both variants or multiple consumer projects.