Claude Code behavior depends heavily on the environment it runs in. Tools available, file paths, shell aliases, language runtimes — all of these affect what Claude Code can do and how reliably it does it. Dev Containers give you a reproducible environment where Claude Code’s behavior is consistent across machines and team members.
This guide covers the practical setup for running Claude Code inside a Dev Container with project-appropriate rules.
Why Dev Containers Change the Rules Equation
When Claude Code runs on a developer’s local machine, it inherits that machine’s environment: the specific Node version installed, whatever global packages exist, local aliases and PATH additions. When a second developer opens the same project on their machine, Claude Code runs in a subtly different environment.
This creates CLAUDE.md problems. Rules like “run npm run test to validate changes” silently break when one machine has Node 18 and another has Node 20, or when npm run test depends on a globally installed package that some team members have and others don’t.
Dev Containers standardize the environment Claude Code operates in. The same CLAUDE.md rules work because the underlying environment is identical.
Basic devcontainer.json Setup
A minimal setup for a TypeScript/Node.js project:
{
"name": "My Project",
"image": "mcr.microsoft.com/devcontainers/typescript-node:1-20",
"features": {
"ghcr.io/devcontainers/features/github-cli:1": {}
},
"postCreateCommand": "npm install",
"customizations": {
"vscode": {
"extensions": [
"anthropics.claude-code"
]
}
},
"mounts": [
"source=${localEnv:HOME}/.claude,target=/home/node/.claude,type=bind,consistency=cached"
]
}
The key detail: mounting ~/.claude from the host into the container. This carries your Claude Code session data, settings, and skills into the container environment without baking them into the image.
Mounting Claude Configuration
Claude Code stores its configuration in ~/.claude/. By default, this directory does not exist inside the container. You have two options:
Option 1: Bind mount from host (recommended for individual developers)
"mounts": [
"source=${localEnv:HOME}/.claude,target=/home/node/.claude,type=bind,consistency=cached"
]
This gives you your personal Claude Code settings inside the container. Skills, settings.json, and session history all carry over.
Option 2: Project-scoped settings only (recommended for teams)
Do not mount ~/.claude. Instead, rely entirely on .claude/settings.json in the project root. This ensures Claude Code behaves identically for every team member regardless of their personal settings.
// .claude/settings.json — committed to the repo
{
"permissions": {
"allow": [
"Bash(npm run *)",
"Bash(git status)",
"Bash(git diff *)",
"Bash(docker *)"
]
}
}
CLAUDE.md for Container Environments
When Claude Code runs in a container, some of the standard CLAUDE.md advice changes:
## Environment
This project runs in a Dev Container based on Node 20 LTS. All development happens inside the container.
### Available Tools
- Node 20.x, npm 10.x
- Git (configured with host credentials via forwarded SSH agent)
- GitHub CLI (`gh`) — authenticated via GITHUB_TOKEN environment variable
- Docker CLI (connects to host Docker daemon via socket mount)
### Running Tests
- `npm run test` — runs Jest test suite
- `npm run test:watch` — runs in watch mode (do not use in CI scripts)
- `npm run test:coverage` — generates coverage report
### Common Pitfalls in This Environment
- Do not use `localhost` to refer to the host machine. Use `host.docker.internal` instead.
- The `/workspace` path is the project root. Do not assume `/Users/...` paths exist.
- Ports forwarded from the container: 3000 (app), 5432 (database), 6379 (Redis).
### Database
- PostgreSQL running as a service in the container
- Connection: `postgresql://postgres:postgres@localhost:5432/mydb`
- Migrations: `npm run db:migrate`
- Seed: `npm run db:seed`
The Common Pitfalls section is important. Container environments break assumptions that work on local machines, and listing them explicitly prevents Claude Code from generating localhost references that fail inside the container.
Adding Services with Docker Compose
For projects that need multiple services (database, cache, external APIs), use docker-compose.yml alongside the devcontainer:
# docker-compose.yml
version: '3.8'
services:
app:
build:
context: .
dockerfile: .devcontainer/Dockerfile
volumes:
- .:/workspace:cached
- ~/.claude:/home/node/.claude:cached
environment:
- DATABASE_URL=postgresql://postgres:postgres@db:5432/mydb
- REDIS_URL=redis://redis:6379
db:
image: postgres:16
environment:
POSTGRES_PASSWORD: postgres
POSTGRES_DB: mydb
volumes:
- postgres_data:/var/lib/postgresql/data
redis:
image: redis:7-alpine
volumes:
postgres_data:
Reference this in devcontainer.json:
{
"dockerComposeFile": ["../docker-compose.yml"],
"service": "app",
"workspaceFolder": "/workspace"
}
Update CLAUDE.md to reflect the actual hostnames:
### Service Endpoints (inside container)
- Database: `postgresql://postgres:postgres@db:5432/mydb`
- Redis: `redis://redis:6379`
- Note: use service names (`db`, `redis`), not `localhost`, for inter-container communication
MCP Servers in Container Environments
MCP servers that reference local file paths need adjustment for container environments. A filesystem MCP server configured for /Users/alice/projects will not find that path inside the container.
The fix is to configure MCP servers using paths that exist inside the container:
// .claude/settings.json
{
"mcpServers": {
"filesystem": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/workspace"]
},
"postgres": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-postgres"],
"env": {
"POSTGRES_CONNECTION_STRING": "postgresql://postgres:postgres@db:5432/mydb"
}
}
}
}
Commit this file. Everyone on the team gets the same MCP configuration when they open the project in a Dev Container.
Team Onboarding with Dev Containers
The primary value of Dev Containers for Claude Code is onboarding speed. A new team member who opens the project in a Dev Container gets:
- The correct language runtime and tool versions
- The project’s CLAUDE.md rules already in place
- The
.claude/settings.jsonpermissions configured - MCP servers connected to the right endpoints
Without Dev Containers, each of these requires manual setup steps that are documented somewhere, partially outdated, and skipped differently by each developer.
In your CLAUDE.md, document the onboarding flow:
## Onboarding
If you just cloned this repo, open it in the Dev Container before starting:
1. Install the Dev Containers VS Code extension
2. Open the Command Palette → "Dev Containers: Reopen in Container"
3. Wait for the container to build and `postCreateCommand` to complete
4. Run `npm run dev` to start the development server
The container includes everything needed. Do not install additional global packages — add them to the Dockerfile instead.
Sharing Claude Code Skills Across the Team
Skills live in ~/.claude/skills/ by default — outside the project, not committed to the repo. For team-shared skills, use project-level skills instead:
.claude/
skills/
review.md
deploy-check.md
db-migration.md
settings.json
These commit to the repo and are available to anyone who opens the project. When running in a Dev Container with the host ~/.claude mounted, Claude Code sees both personal skills and project skills.
This is the recommended setup for teams: project skills for shared workflows, personal skills for individual preferences.
Dockerfile Best Practices for Claude Code Projects
When building a custom image for your Dev Container:
FROM mcr.microsoft.com/devcontainers/typescript-node:1-20
# Install project-specific tools Claude Code might invoke
RUN npm install -g tsx @anthropic-ai/claude-code
# Set up consistent locale (prevents encoding issues in Claude Code output)
ENV LANG=en_US.UTF-8
ENV LC_ALL=en_US.UTF-8
# Pre-install MCP server dependencies
RUN npm install -g @modelcontextprotocol/server-filesystem \
@modelcontextprotocol/server-github
# Add project-specific tools (linters, formatters, etc.)
RUN npm install -g prettier eslint
Pre-installing tools in the Dockerfile means Claude Code can invoke them without downloading them at runtime, which avoids permission issues and improves reliability.
Summary
Dev Containers make Claude Code’s behavior reproducible across the team. The key practices:
- Mount
~/.claudefor personal settings, or omit it for strict team-uniform behavior - Document container-specific environment facts in CLAUDE.md (hostnames, paths, ports)
- Commit
.claude/settings.jsonand.claude/skills/for shared configuration - Configure MCP servers with container-relative paths, not host paths
- Pre-install tools in the Dockerfile that Claude Code needs to invoke
The upfront investment is a Dockerfile and a devcontainer.json. The return is a team where everyone’s Claude Code setup is effectively identical.