DevContainers: Consistent Development Environments Across Teams
Introduction
Development environment inconsistencies cause "works on my machine" problems that waste time and frustrate teams. DevContainers solve this by defining development environments as code, ensuring every developer works in an identical, reproducible environment regardless of their local machine setup.
DevContainer Fundamentals
Basic Configuration
.devcontainer/devcontainer.json:
{
"name": "Node.js Development",
"image": "mcr.microsoft.com/devcontainers/javascript-node:20",
"features": {
"ghcr.io/devcontainers/features/git:1": {},
"ghcr.io/devcontainers/features/github-cli:1": {}
},
"customizations": {
"vscode": {
"extensions": [
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode",
"ms-azuretools.vscode-docker"
],
"settings": {
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"eslint.validate": ["javascript", "typescript"]
}
}
},
"forwardPorts": [3000, 9229],
"postCreateCommand": "npm install",
"remoteUser": "node"
}
Custom Dockerfile
.devcontainer/Dockerfile:
FROM mcr.microsoft.com/devcontainers/python:3.11
# Install system dependencies
RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
&& apt-get -y install --no-install-recommends \
postgresql-client \
redis-tools \
curl \
jq
# Install Python tools
RUN pip install --upgrade pip \
&& pip install black pylint pytest pytest-cov
# Setup non-root user
ARG USERNAME=vscode
ARG USER_UID=1000
ARG USER_GID=$USER_UID
USER $USERNAME
# Configure shell
RUN echo 'export PS1="\[\e[32m\](devcontainer)\[\e[m\] \w $ "' >> ~/.bashrc
devcontainer.json with Dockerfile:
{
"name": "Python Development",
"build": {
"dockerfile": "Dockerfile",
"args": {
"USERNAME": "vscode"
}
},
"customizations": {
"vscode": {
"extensions": [
"ms-python.python",
"ms-python.vscode-pylance",
"ms-python.black-formatter"
]
}
},
"postCreateCommand": "pip install -r requirements.txt",
"remoteUser": "vscode"
}
Multi-Container Development
Docker Compose Integration
.devcontainer/docker-compose.yml:
version: '3.8'
services:
app:
build:
context: ..
dockerfile: .devcontainer/Dockerfile
volumes:
- ..:/workspace:cached
command: sleep infinity
networks:
- dev-network
depends_on:
- db
- redis
db:
image: postgres:16
restart: unless-stopped
environment:
POSTGRES_USER: devuser
POSTGRES_PASSWORD: devpassword
POSTGRES_DB: devdb
volumes:
- postgres-data:/var/lib/postgresql/data
networks:
- dev-network
redis:
image: redis:7
restart: unless-stopped
networks:
- dev-network
volumes:
postgres-data:
networks:
dev-network:
devcontainer.json with Compose:
{
"name": "Full Stack Development",
"dockerComposeFile": "docker-compose.yml",
"service": "app",
"workspaceFolder": "/workspace",
"customizations": {
"vscode": {
"extensions": [
"ms-azuretools.vscode-docker",
"ms-python.python",
"cweijan.vscode-postgresql-client2"
]
}
},
"forwardPorts": [8000, 5432, 6379],
"postCreateCommand": "pip install -r requirements.txt && python manage.py migrate",
"remoteUser": "vscode"
}
Language-Specific Configurations
Python with Virtual Environment
.devcontainer/devcontainer.json:
{
"name": "Python 3.11",
"image": "mcr.microsoft.com/devcontainers/python:3.11",
"features": {
"ghcr.io/devcontainers/features/common-utils:2": {
"installZsh": true,
"configureZshAsDefaultShell": true
}
},
"customizations": {
"vscode": {
"extensions": [
"ms-python.python",
"ms-python.vscode-pylance",
"ms-python.black-formatter",
"ms-python.isort",
"njpwerner.autodocstring"
],
"settings": {
"python.defaultInterpreterPath": "/usr/local/bin/python",
"python.linting.enabled": true,
"python.linting.pylintEnabled": true,
"python.formatting.provider": "black",
"python.testing.pytestEnabled": true,
"[python]": {
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.organizeImports": true
}
}
}
}
},
"postCreateCommand": "pip install -r requirements.txt -r requirements-dev.txt",
"postStartCommand": "python --version && pip list"
}
.NET with SDK
.devcontainer/devcontainer.json:
{
"name": ".NET 8.0",
"image": "mcr.microsoft.com/devcontainers/dotnet:8.0",
"features": {
"ghcr.io/devcontainers/features/azure-cli:1": {},
"ghcr.io/devcontainers/features/powershell:1": {}
},
"customizations": {
"vscode": {
"extensions": [
"ms-dotnettools.csharp",
"ms-dotnettools.csdevkit",
"ms-dotnettools.vscode-dotnet-runtime",
"visualstudioexptteam.vscodeintellicode-csharp"
],
"settings": {
"omnisharp.enableRoslynAnalyzers": true,
"omnisharp.enableEditorConfigSupport": true,
"dotnet.defaultSolution": "MyApp.sln"
}
}
},
"postCreateCommand": "dotnet restore && dotnet build",
"remoteUser": "vscode"
}
Node.js with npm/Yarn
.devcontainer/devcontainer.json:
{
"name": "Node.js 20 with TypeScript",
"image": "mcr.microsoft.com/devcontainers/typescript-node:20",
"features": {
"ghcr.io/devcontainers/features/node:1": {
"version": "20",
"nodeGypDependencies": true
}
},
"customizations": {
"vscode": {
"extensions": [
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode",
"ms-vscode.vscode-typescript-next",
"orta.vscode-jest"
],
"settings": {
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
},
"typescript.tsdk": "node_modules/typescript/lib"
}
}
},
"postCreateCommand": "npm install",
"postStartCommand": "npm run dev",
"forwardPorts": [3000, 5173],
"portsAttributes": {
"3000": {
"label": "Application",
"onAutoForward": "notify"
}
}
}
Java with Maven
.devcontainer/devcontainer.json:
{
"name": "Java 21 with Maven",
"image": "mcr.microsoft.com/devcontainers/java:21",
"features": {
"ghcr.io/devcontainers/features/java:1": {
"version": "21",
"installMaven": true,
"installGradle": false
}
},
"customizations": {
"vscode": {
"extensions": [
"vscjava.vscode-java-pack",
"vscjava.vscode-spring-boot-dashboard",
"vmware.vscode-spring-boot"
],
"settings": {
"java.configuration.runtimes": [
{
"name": "JavaSE-21",
"path": "/usr/local/sdkman/candidates/java/current"
}
]
}
}
},
"postCreateCommand": "mvn clean install -DskipTests",
"forwardPorts": [8080]
}
VS Code Integration
Remote - Containers Extension
Installation:
# Install extension
code --install-extension ms-vscode-remote.remote-containers
Usage:
- Open in Container:
Ctrl+Shift+P→ "Dev Containers: Open Folder in Container" - Rebuild Container:
Ctrl+Shift+P→ "Dev Containers: Rebuild Container" - Reopen Locally:
Ctrl+Shift+P→ "Dev Containers: Reopen Folder Locally"
Launch Configuration
.vscode/launch.json:
{
"version": "0.2.0",
"configurations": [
{
"name": "Python: FastAPI",
"type": "python",
"request": "launch",
"module": "uvicorn",
"args": [
"main:app",
"--reload",
"--host", "0.0.0.0",
"--port", "8000"
],
"jinja": true,
"justMyCode": false
},
{
"name": "Node: Debug",
"type": "node",
"request": "launch",
"program": "${workspaceFolder}/src/index.js",
"restart": true,
"console": "integratedTerminal"
}
]
}
Tasks Configuration
.vscode/tasks.json:
{
"version": "2.0.0",
"tasks": [
{
"label": "Run Tests",
"type": "shell",
"command": "pytest tests/ -v --cov",
"group": {
"kind": "test",
"isDefault": true
},
"presentation": {
"reveal": "always",
"panel": "new"
}
},
{
"label": "Start Database",
"type": "shell",
"command": "docker-compose up -d db",
"problemMatcher": []
}
]
}
GitHub Codespaces Integration
Configuration
.devcontainer/devcontainer.json for Codespaces:
{
"name": "Codespaces Python",
"image": "mcr.microsoft.com/devcontainers/python:3.11",
"hostRequirements": {
"cpus": 4,
"memory": "8gb",
"storage": "32gb"
},
"customizations": {
"codespaces": {
"openFiles": [
"README.md",
"src/main.py"
]
}
},
"secrets": {
"GITHUB_TOKEN": {
"description": "GitHub Personal Access Token"
}
},
"postCreateCommand": "pip install -r requirements.txt && pre-commit install"
}
Lifecycle Scripts
.devcontainer/lifecycle/postCreate.sh:
#!/bin/bash
echo "Setting up development environment..."
# Install dependencies
pip install -r requirements.txt
# Setup pre-commit hooks
pre-commit install
# Initialize database
python manage.py migrate
# Create sample data
python manage.py loaddata fixtures/sample_data.json
echo "Setup complete! Ready to code."
Reference in devcontainer.json:
{
"postCreateCommand": "bash .devcontainer/lifecycle/postCreate.sh",
"postStartCommand": "python manage.py runserver 0.0.0.0:8000",
"postAttachCommand": "git status"
}
Performance Optimization
Volume Mounts
Cached Mounts:
{
"mounts": [
"source=${localWorkspaceFolder},target=/workspace,type=bind,consistency=cached"
]
}
Named Volumes for Dependencies:
{
"mounts": [
"source=node_modules,target=${containerWorkspaceFolder}/node_modules,type=volume",
"source=pip-cache,target=/home/vscode/.cache/pip,type=volume"
]
}
BuildKit Cache
.devcontainer/Dockerfile with cache:
# syntax=docker/dockerfile:1
FROM mcr.microsoft.com/devcontainers/python:3.11
# Cache pip dependencies
RUN --mount=type=cache,target=/root/.cache/pip \
pip install --upgrade pip setuptools wheel
# Copy and install requirements with cache
COPY requirements.txt /tmp/
RUN --mount=type=cache,target=/root/.cache/pip \
pip install -r /tmp/requirements.txt
Layer Optimization
# Bad: Combines unrelated operations
RUN apt-get update && apt-get install -y \
postgresql-client \
&& pip install django
# Good: Separate layers for caching
RUN apt-get update && apt-get install -y postgresql-client
RUN pip install django
Team Adoption
Documentation
DEVCONTAINER_SETUP.md:
# DevContainer Setup
## Prerequisites
- Docker Desktop 4.20+
- VS Code with Remote - Containers extension
- Git 2.30+
## Getting Started
1. Clone repository: `git clone https://github.com/contoso/myapp.git`
2. Open in VS Code: `code myapp`
3. Press `Ctrl+Shift+P` → "Dev Containers: Reopen in Container"
4. Wait for container build (5-10 minutes first time)
5. Run application: `npm start`
## Troubleshooting
### Container Build Fails
```bash
# Clear Docker cache
docker system prune -a --volumes
Port Already in Use
Edit .devcontainer/devcontainer.json and change forwardPorts.
### Sharing Configurations
**`.devcontainer/devcontainer.json` in version control:**
```bash
# Commit devcontainer configuration
git add .devcontainer/
git commit -m "Add DevContainer configuration"
git push origin main
Organization Templates:
# Create template repository
git clone https://github.com/contoso/devcontainer-templates.git
cp -r devcontainer-templates/python/.devcontainer ./
Best Practices
- Version Control Everything: Commit all
.devcontainerfiles to repository - Use Official Images: Prefer
mcr.microsoft.com/devcontainers/*images - Minimal Base Images: Start with minimal images, add only required tools
- Non-Root User: Always specify
remoteUserfor security - Port Forwarding: Document all forwarded ports in README
- Lifecycle Scripts: Use
postCreateCommandfor one-time setup only - Secrets Management: Never hardcode secrets in devcontainer.json
Troubleshooting
Container Build Timeout:
# Increase Docker memory allocation
Docker Desktop → Settings → Resources → Memory: 8GB
Permission Errors:
{
"remoteUser": "vscode",
"containerUser": "vscode",
"updateRemoteUserUID": true
}
Extension Not Loading:
# Reinstall extensions in container
Ctrl+Shift+P → "Dev Containers: Rebuild Container Without Cache"
Key Takeaways
- DevContainers eliminate "works on my machine" problems with reproducible environments
- Configuration as code enables consistent development across teams
- Docker Compose supports multi-container development with databases and services
- GitHub Codespaces provides cloud-based DevContainer environments
- Performance optimizations like cached mounts and BuildKit reduce rebuild times
Next Steps
- Explore Features for reusable DevContainer components
- Implement Dev Container Templates for common project types
- Integrate GitHub Actions to validate DevContainer builds in CI
- Configure Pre-commit hooks within DevContainers for code quality
Additional Resources
- DevContainers Specification
- VS Code Remote Development
- GitHub Codespaces Documentation
- DevContainer Features
Build once, develop anywhere.