Dev Containers are Docker containers specifically configured to serve as full-featured development environments within Visual Studio Code. They provide an isolated, reproducible workspace where you can develop without cluttering your host system or worrying about dependency conflicts.
- Consistency: Everyone on your team uses the exact same environment
- Isolation: Keep project dependencies separate from your host system
- Security: Sandbox tools like AI coding assistants (Claude Code, GitHub Copilot) so they can't access your SSH keys, secrets, or personal files
- Portability: Share your entire development setup with a single configuration file
- Clean Setup: New contributors can start coding in minutes without complex setup instructions
Dev Containers are especially valuable when using Claude Code because you can safely run it with --dangerously-skip-permissions flag inside the container. This gives Claude full freedom to work efficiently while keeping your host system protected.
Before starting, install:
- Docker Desktop (or Docker Engine on Linux)
- Visual Studio Code
- Dev Containers extension for VS Code (install from Extensions marketplace)
Every dev container project needs a .devcontainer folder in your project root:
your-project/
├── .devcontainer/
│ └── devcontainer.json
├── src/
└── [your project files]
Here's a minimal configuration that works for any project:
{
"name": "My Development Container",
"image": "mcr.microsoft.com/devcontainers/base:ubuntu",
"features": {},
"customizations": {
"vscode": {
"extensions": [],
"settings": {}
}
},
"postCreateCommand": "",
"remoteUser": "vscode"
}The image property defines your base container. Choose based on your project type:
// Generic Linux base
"image": "mcr.microsoft.com/devcontainers/base:ubuntu"
// Node.js / JavaScript / TypeScript
"image": "mcr.microsoft.com/devcontainers/typescript-node:1-20"
// Python
"image": "mcr.microsoft.com/devcontainers/python:1-3.11"
// Go
"image": "mcr.microsoft.com/devcontainers/go:1.21"
// Rust
"image": "mcr.microsoft.com/devcontainers/rust:1-bullseye"
// Java
"image": "mcr.microsoft.com/devcontainers/java:1-17"
// .NET
"image": "mcr.microsoft.com/devcontainers/dotnet:1-7.0"
// PHP
"image": "mcr.microsoft.com/devcontainers/php:1-8.2"
// Ruby
"image": "mcr.microsoft.com/devcontainers/ruby:1-3"Features add tools and capabilities to your container:
"features": {
// Essential utilities (git, curl, etc.)
"ghcr.io/devcontainers/features/common-utils:2": {},
// Git
"ghcr.io/devcontainers/features/git:1": {},
// Docker-in-Docker
"ghcr.io/devcontainers/features/docker-in-docker:2": {
"version": "latest",
"enableNonRootDocker": true,
"moby": true
},
// Node.js (if not in base image)
"ghcr.io/devcontainers/features/node:1": {
"version": "20"
},
// Python (if not in base image)
"ghcr.io/devcontainers/features/python:1": {
"version": "3.11"
},
// AWS CLI
"ghcr.io/devcontainers/features/aws-cli:1": {},
// GitHub CLI
"ghcr.io/devcontainers/features/github-cli:1": {}
}Browse all available features at: https://containers.dev/features
Install extensions and configure settings:
"customizations": {
"vscode": {
"extensions": [
// Add language-specific extensions
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode",
"ms-python.python",
"golang.go"
],
"settings": {
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"files.autoSave": "onFocusChange"
}
}
}Run commands at different stages:
// Run once after container is created
"postCreateCommand": "npm install && npm run build"
// Run every time container starts
"postStartCommand": "docker-compose up -d postgres"
// Run before container is attached
"postAttachCommand": "echo 'Welcome to the dev container!'"Make container ports accessible from your host:
"forwardPorts": [3000, 8080, 5432],
// Advanced port configuration
"portsAttributes": {
"3000": {
"label": "Application",
"onAutoForward": "notify"
},
"5432": {
"label": "PostgreSQL",
"onAutoForward": "silent"
}
}Set environment variables in the container:
"containerEnv": {
"NODE_ENV": "development",
"DEBUG": "true",
"DATABASE_URL": "postgresql://localhost:5432/mydb"
}Mount files or directories from your host:
"mounts": [
// Mount Docker socket for Docker-in-Docker
"source=/var/run/docker.sock,target=/var/run/docker.sock,type=bind",
// Mount a local directory
"source=${localWorkspaceFolder}/data,target=/workspace/data,type=bind"
]{
"name": "Web Application Dev Container",
"image": "mcr.microsoft.com/devcontainers/typescript-node:1-20",
"features": {
"ghcr.io/devcontainers/features/common-utils:2": {},
"ghcr.io/devcontainers/features/git:1": {},
"ghcr.io/devcontainers/features/docker-in-docker:2": {
"version": "latest",
"enableNonRootDocker": true
}
},
"customizations": {
"vscode": {
"extensions": [
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode",
"bradlc.vscode-tailwindcss"
],
"settings": {
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode"
}
}
},
"postCreateCommand": "npm install",
"forwardPorts": [3000, 5173, 8080],
"remoteUser": "node"
}{
"name": "Python Data Science",
"image": "mcr.microsoft.com/devcontainers/python:1-3.11",
"features": {
"ghcr.io/devcontainers/features/common-utils:2": {},
"ghcr.io/devcontainers/features/git:1": {}
},
"customizations": {
"vscode": {
"extensions": [
"ms-python.python",
"ms-python.vscode-pylance",
"ms-toolsai.jupyter"
]
}
},
"postCreateCommand": "pip install -r requirements.txt",
"forwardPorts": [8888],
"remoteUser": "vscode"
}{
"name": "Full-Stack Application",
"image": "mcr.microsoft.com/devcontainers/typescript-node:1-20",
"features": {
"ghcr.io/devcontainers/features/common-utils:2": {},
"ghcr.io/devcontainers/features/git:1": {},
"ghcr.io/devcontainers/features/docker-in-docker:2": {
"version": "latest",
"enableNonRootDocker": true
}
},
"customizations": {
"vscode": {
"extensions": [
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode",
"ms-azuretools.vscode-docker"
]
}
},
"postCreateCommand": "npm install",
"postStartCommand": "docker-compose up -d",
"forwardPorts": [3000, 5432, 6379, 8080],
"remoteUser": "node"
}-
Create the configuration
- Create
.devcontainer/devcontainer.jsonin your project root - Configure it based on your project needs
- Create
-
Open in container
- Open your project folder in VS Code
- You'll see a notification: "Reopen in Container" - click it
- Or press
F1→Dev Containers: Reopen in Container
-
Wait for build
- VS Code downloads the image and builds your container
- Extensions are installed automatically
postCreateCommandruns
-
Start developing
- Your terminal is now inside the container
- All tools and dependencies are ready
- Opening the project: VS Code remembers and automatically reopens in the container
- Closing: Just close VS Code normally
- Rebuilding:
F1→Dev Containers: Rebuild Container(after config changes) - Going back to local:
F1→Dev Containers: Reopen Folder Locally
Your container doesn't have access to your local SSH keys by default (for security). Here's how to authenticate:
- Create a token at: https://github.com/settings/personal-access-tokens/new
- Give it "Contents: Read and write" permission
- Clone with the token:
git clone https://<USERNAME>:<TOKEN>@github.com/<USERNAME>/<REPO>.git
- Or add it to existing repo:
git remote add origin https://<USERNAME>:<TOKEN>@github.com/<USERNAME>/<REPO>.git
Add to your devcontainer.json:
"mounts": [
"source=${localEnv:HOME}/.ssh,target=/home/vscode/.ssh,readonly,type=bind"
]To use Claude Code inside your dev container, you need to install it first. There are two approaches:
Once inside the container terminal, install Claude Code globally:
npm install -g @anthropic-ai/claude-codeAfter installation, you can run Claude Code from any directory:
claude --dangerously-skip-permissionsAdd the installation to your devcontainer.json to automatically install Claude Code every time the container is created:
{
"postCreateCommand": "npm install -g @anthropic-ai/claude-code && npm install"
}Or if you prefer to install it after the container starts:
{
"postStartCommand": "npm install -g @anthropic-ai/claude-code"
}Note: The automatic approach ensures Claude Code is always available without manual installation steps.
One of the best reasons to use dev containers is to safely run Claude Code with full permissions:
If using the Claude Code CLI:
claude --dangerously-skip-permissionsIf using the Claude Code VS Code extension, you can configure it to skip permission prompts by adding settings to your devcontainer.json:
{
"customizations": {
"vscode": {
"settings": {
"claudeCode.mode": "bypassPermissions"
}
}
}
}Alternative: Create a .claude/settings.json file in your project to configure permissions:
{
"mode": "bypassPermissions"
}Other permission modes available:
default- Standard behavior with permission promptsacceptEdits- Automatically accepts file edits onlyplan- Claude can analyze but not modify filesbypassPermissions- Skips all permission prompts (use only in containers)
Claude can now work freely without permission prompts, but:
- It can only access files in the container
- Your SSH keys and secrets on the host are safe
- Your host filesystem is protected
- Worst case: the container breaks, just rebuild it
bypassPermissions mode on your host machine - only use it inside isolated dev containers.
- Check Docker is running
- Verify your
devcontainer.jsonsyntax (must be valid JSON) - Look at the build output for errors
- Ensure ports are forwarded in
forwardPorts - Check if port is already in use on host
- Verify the service is running in the container
- Use the docker-in-docker feature (not just socket mount)
- Set
"enableNonRootDocker": true - Don't mix socket mounts with docker-in-docker feature
- Rebuild the container:
F1→Dev Containers: Rebuild Container - Some changes require a full rebuild, not just restart
- Check
remoteUsersetting - For Docker access, ensure user is in docker group
- Consider using
"remoteUser": "root"for development (less secure)
- Keep it in version control: Commit
.devcontainer/devcontainer.jsonso everyone uses the same setup - Document requirements: Add a README explaining any prerequisites
- Pin versions: Specify exact versions for reproducibility
- Minimize the image: Only include necessary tools
- Use .dockerignore: Exclude unnecessary files from container context
- Test-driven development: Write tests first when using AI assistants
- Regular rebuilds: Rebuild occasionally to get updates
- Separate secrets: Never commit tokens or secrets in devcontainer.json
For multi-container setups, use docker-compose.yml instead:
{
"name": "My App",
"dockerComposeFile": "docker-compose.yml",
"service": "app",
"workspaceFolder": "/workspace"
}For more control, use a custom Dockerfile:
{
"name": "Custom Container",
"build": {
"dockerfile": "Dockerfile",
"context": ".."
}
}Create different configurations for different purposes:
.devcontainer/devcontainer.json- default.devcontainer/frontend/devcontainer.json- frontend.devcontainer/backend/devcontainer.json- backend
- Official Documentation: https://code.visualstudio.com/docs/devcontainers/containers
- Dev Container Spec: https://containers.dev/
- Feature Library: https://containers.dev/features
- Image Library: https://github.com/devcontainers/images
- Example Templates: https://github.com/devcontainers/templates
Dev Containers transform your development workflow by providing consistent, isolated environments that are easy to share and maintain. They're especially powerful when combined with AI coding assistants, giving you the best of both worlds: powerful automation and strong security boundaries.
Start with a simple configuration and gradually add features as needed. Your future self (and your teammates) will thank you!