Devcontainers for Reproducible Development
Dev Containers define your entire development environment as code, ensuring every team member runs the same tools, dependencies, and settings regardless of their host OS. This guide covers creating devcontainer.json configurations, using Dev Container Features, Docker Compose integration, and running Dev Containers from both VS Code and the CLI.
Prerequisites
- Docker installed and running
- VS Code with the Dev Containers extension (for VS Code usage)
- Node.js 18+ (for the Dev Container CLI)
- Basic familiarity with Docker
Understanding Dev Containers
A Dev Container is a Docker container configured via .devcontainer/devcontainer.json at the root of your repository. When you open the project in VS Code (or use the CLI), it:
- Builds or pulls the container image
- Mounts your project files into the container
- Installs VS Code extensions inside the container
- Runs any post-create setup commands
Your code runs inside the container, but your files stay on the host.
Creating Your First devcontainer.json
mkdir -p .devcontainer
// .devcontainer/devcontainer.json
{
"name": "My Project Dev Container",
// Use a pre-built image
"image": "mcr.microsoft.com/devcontainers/python:3.11-bullseye",
// Forward ports from container to host
"forwardPorts": [8000, 5432],
// VS Code extensions to install inside the container
"customizations": {
"vscode": {
"extensions": [
"ms-python.python",
"ms-python.black-formatter",
"ms-python.pylint",
"esbenp.prettier-vscode"
],
"settings": {
"python.defaultInterpreterPath": "/usr/local/bin/python",
"editor.formatOnSave": true
}
}
},
// Commands to run after container creation
"postCreateCommand": "pip install -r requirements.txt",
// Set the remote user (avoids running as root)
"remoteUser": "vscode"
}
Using Dev Container Features
Features are self-contained scripts that install tools into your container without writing custom Dockerfiles:
{
"name": "Node.js Project",
"image": "mcr.microsoft.com/devcontainers/base:ubuntu-22.04",
"features": {
// Node.js LTS
"ghcr.io/devcontainers/features/node:1": {
"version": "20"
},
// Docker-in-Docker for building images
"ghcr.io/devcontainers/features/docker-in-docker:2": {
"version": "latest"
},
// Git with latest version
"ghcr.io/devcontainers/features/git:1": {
"version": "latest",
"ppa": true
},
// GitHub CLI
"ghcr.io/devcontainers/features/github-cli:1": {}
},
"postCreateCommand": "npm install",
"customizations": {
"vscode": {
"extensions": ["esbenp.prettier-vscode", "ms-vscode.vscode-typescript-next"]
}
}
}
Browse available features at containers.dev/features.
Custom Dockerfiles
When you need more control, reference a Dockerfile:
# .devcontainer/Dockerfile
FROM mcr.microsoft.com/devcontainers/base:ubuntu-22.04
# Install system dependencies
RUN apt-get update && apt-get install -y \
build-essential \
libpq-dev \
redis-tools \
&& rm -rf /var/lib/apt/lists/*
# Install specific Go version
RUN curl -fsSL https://go.dev/dl/go1.21.0.linux-amd64.tar.gz \
| tar -C /usr/local -xzf -
ENV PATH=$PATH:/usr/local/go/bin
# Install project tools
RUN go install github.com/air-verse/air@latest
USER vscode
Reference it in devcontainer.json:
{
"name": "Go Project",
"build": {
"dockerfile": "Dockerfile",
"context": "..",
"args": {
"GO_VERSION": "1.21"
}
},
"postCreateCommand": "go mod download",
"forwardPorts": [8080]
}
Docker Compose Integration
For projects with multiple services (app + database + cache):
# .devcontainer/docker-compose.yml
version: '3.8'
services:
app:
build:
context: ..
dockerfile: .devcontainer/Dockerfile
volumes:
- ..:/workspace:cached
- /var/run/docker.sock:/var/run/docker.sock
command: sleep infinity # Keep container running
depends_on:
- db
- redis
db:
image: postgres:15-alpine
environment:
POSTGRES_DB: myapp
POSTGRES_USER: devuser
POSTGRES_PASSWORD: devpassword
volumes:
- postgres-data:/var/lib/postgresql/data
redis:
image: redis:7-alpine
volumes:
postgres-data:
// .devcontainer/devcontainer.json
{
"name": "Full Stack Dev",
"dockerComposeFile": "docker-compose.yml",
"service": "app", // Which service is the dev container
"workspaceFolder": "/workspace",
"forwardPorts": [3000, 5432, 6379],
"postCreateCommand": "npm install && npx prisma migrate dev",
"customizations": {
"vscode": {
"extensions": ["ms-vscode.vscode-node-azure-pack"]
}
}
}
VS Code Usage
- Install the Dev Containers extension (
ms-vscode-remote.remote-containers) - Open your repository in VS Code
- Click the green
><button in the bottom-left corner - Select Reopen in Container
VS Code builds the container, installs extensions, and reopens in the container. The integrated terminal runs inside the container.
Useful commands (via Command Palette Ctrl+Shift+P):
Dev Containers: Rebuild Container- Rebuild after Dockerfile changesDev Containers: Open Container Configuration File- Editdevcontainer.jsonDev Containers: Show Container Log- Debug build issuesDev Containers: Clone Repository in Container Volume- For better performance on macOS
Dev Container CLI Usage
For CI/CD or non-VS Code workflows:
# Install the CLI
npm install -g @devcontainers/cli
# Build and start the dev container
devcontainer up --workspace-folder .
# Execute a command inside the container
devcontainer exec --workspace-folder . npm test
# Build only (for CI caching)
devcontainer build --workspace-folder . --image-name myapp-devcontainer:latest
# Use in CI (GitHub Actions example)
GitHub Actions workflow using a Dev Container:
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Install Dev Container CLI
run: npm install -g @devcontainers/cli
- name: Run tests in dev container
run: |
devcontainer up --workspace-folder .
devcontainer exec --workspace-folder . npm test
Team Standardization
Best practices for consistent team environments:
{
// Pin image versions for reproducibility
"image": "mcr.microsoft.com/devcontainers/python:3.11.6-bullseye",
// Mount SSH agent for Git operations
"mounts": [
"source=${localEnv:SSH_AUTH_SOCK},target=/ssh-agent,type=bind"
],
"remoteEnv": {
"SSH_AUTH_SOCK": "/ssh-agent"
},
// Configure Git inside container
"postCreateCommand": "git config --global core.autocrlf false",
// Lifecycle scripts
"postStartCommand": "git fetch --all",
"initializeCommand": "echo 'Starting dev environment...'",
// Share dotfiles
"dotfiles": {
"repository": "https://github.com/yourteam/dotfiles",
"installCommand": "install.sh"
}
}
Troubleshooting
Container fails to build:
# Check build logs in VS Code: Command Palette > "Dev Containers: Show Container Log"
# Or via CLI with verbose output
devcontainer up --workspace-folder . --log-level debug
Extensions not installing:
# Verify extension IDs are correct (find on marketplace URL)
# Check internet connectivity from inside container:
devcontainer exec --workspace-folder . curl -I https://marketplace.visualstudio.com
File permission issues:
# Add to devcontainer.json to fix ownership
"postCreateCommand": "sudo chown -R vscode:vscode /workspace"
Slow file I/O on macOS:
Use named volumes for the workspace mount to bypass macOS file sharing overhead:
{
"workspaceMount": "source=myproject-volume,target=/workspace,type=volume",
"workspaceFolder": "/workspace"
}
Conclusion
Dev Containers eliminate "works on my machine" problems by encoding the entire development environment as version-controlled configuration. By combining Features for tool installation, Docker Compose for multi-service setups, and the Dev Container CLI for CI integration, teams achieve consistent, reproducible environments from day one. Store devcontainer.json in source control so new team members are productive in minutes.


