TL;DR
Dev Containers define your entire development environment as code using a devcontainer.json file, ensuring every developer gets an identical, reproducible setup with a single click. In 2026, Dev Containers are natively supported in VS Code, JetBrains IDEs, GitHub Codespaces, and CI/CD pipelines. They eliminate "works on my machine" issues by containerizing editor settings, extensions, tools, runtimes, databases, and services into a portable, version-controlled configuration.
Key Takeaways
- devcontainer.json is an open specification supported by VS Code, JetBrains, GitHub Codespaces, and CLI tools
- Pre-built images from mcr.microsoft.com/devcontainers cover most languages out of the box
- The Features system lets you compose tools without writing Dockerfiles
- Docker Compose integration enables multi-service dev environments with databases and caches
- GitHub Codespaces provides cloud-hosted dev containers accessible from any browser
- Lifecycle commands automate dependency installation and service startup
- GPU passthrough enables ML/AI development inside containers with CUDA support
- The devcontainer CLI enables CI/CD pipelines to use the same environment developers use locally
Table of Contents
- What Are Dev Containers
- Dev Containers vs Docker Compose vs Vagrant vs Nix
- devcontainer.json Specification
- VS Code Setup
- GitHub Codespaces
- Pre-built Images
- Custom Dockerfile
- Features System
- Docker Compose Multi-Service
- Port Forwarding
- Environment Variables and Secrets
- VS Code Extensions and Settings
- Lifecycle Commands
- Volumes and Mounts
- GPU Support for ML/AI
- Multi-Root Workspaces
- Templates and Community Features
- JetBrains Support
- Performance Optimization
- Team Onboarding
- CI/CD Integration
- Security Best Practices
- Common Language Patterns
- Troubleshooting
The "works on my machine" problem has plagued software teams for decades. Different operating systems, tool versions, and system configurations cause builds to fail and onboarding to take days. Dev Containers solve this by defining your entire development environment as a single JSON file in your repository. In 2026, the specification has matured into an open standard supported by every major editor and cloud platform.
What Are Dev Containers and Why They Matter
A Dev Container is a Docker container specifically configured for development. Unlike production containers, dev containers include editors, debuggers, language runtimes, linters, and every tool a developer needs. The configuration lives in .devcontainer/devcontainer.json, making it version-controlled and shared across the team.
Why Dev Containers Matter in 2026
- Zero onboarding friction: new team members get a working environment in minutes
- Reproducible builds: everyone runs the exact same tools and versions
- OS independence: same devcontainer.json works on macOS, Windows, and Linux
- Cloud-ready: runs locally or instantly in GitHub Codespaces from a browser
- Open specification: supported by VS Code, JetBrains, DevPod, and CLI tools
- CI/CD parity: test and build inside the same container developers use
Dev Containers vs Docker Compose vs Vagrant vs Nix
Dev Containers are not the only solution for reproducible environments. Here is how they compare to other popular approaches.
| Feature | Dev Containers | Docker Compose | Vagrant | Nix |
|---|---|---|---|---|
| Purpose | Dev environment as code | Multi-container orchestration | VM-based dev environments | Reproducible packages |
| Editor Integration | VS Code, JetBrains native | None | SSH only | direnv + plugins |
| Cloud Support | Codespaces, DevPod | None | None | None |
| Startup Speed | Seconds (cached) | Seconds | Minutes (VM) | Seconds (cached) |
| Resource Usage | Low (containers) | Low (containers) | High (full VM) | Minimal (host) |
| Learning Curve | Low-Medium | Medium | Medium | High |
| Multi-Service | Via Docker Compose | Native | Multiple VMs | NixOS containers |
| OS Isolation | Linux container | Linux container | Full VM (any OS) | None (host) |
devcontainer.json Specification and Structure
The devcontainer.json file is the heart of every Dev Container. It follows the open specification at containers.dev and lives in .devcontainer/devcontainer.json.
// .devcontainer/devcontainer.json — full reference
{
"image": "mcr.microsoft.com/devcontainers/typescript-node:20",
// OR: "build": { "dockerfile": "Dockerfile", "context": ".." },
// OR: "dockerComposeFile": "docker-compose.yml", "service": "app",
"name": "My Project Dev",
"features": {
"ghcr.io/devcontainers/features/node:1": { "version": "20" },
"ghcr.io/devcontainers/features/python:1": { "version": "3.12" },
"ghcr.io/devcontainers/features/docker-in-docker:2": {}
},
"forwardPorts": [3000, 5432, 6379],
"portsAttributes": {
"3000": { "label": "App", "onAutoForward": "notify" },
"5432": { "label": "PostgreSQL", "onAutoForward": "silent" }
},
"customizations": {
"vscode": {
"extensions": ["dbaeumer.vscode-eslint", "esbenp.prettier-vscode"],
"settings": { "editor.formatOnSave": true }
}
},
"containerEnv": { "NODE_ENV": "development" },
"remoteEnv": { "LOCAL_USER": "\${localEnv:USER}" },
"postCreateCommand": "npm install && npx prisma migrate dev",
"postStartCommand": "npm run dev",
"remoteUser": "node",
"mounts": [
"source=node-modules,target=/workspace/node_modules,type=volume",
"source=\${localEnv:HOME}/.ssh,target=/home/node/.ssh,type=bind,readonly"
],
"runArgs": ["--memory=4g", "--cpus=2"]
}VS Code Dev Containers Extension Setup
VS Code provides first-class support through the Dev Containers extension. After installing it, VS Code detects .devcontainer/devcontainer.json and offers to reopen the folder inside a container.
The extension requires Docker Desktop (or Docker Engine on Linux) to be installed and running. On macOS and Windows, Docker Desktop provides the Docker daemon and a Linux VM for running containers. On Linux, Docker Engine runs natively with the best performance.
# Step 1: Install prerequisites
# macOS/Windows: Install Docker Desktop from docker.com
# Linux: Install Docker Engine
curl -fsSL https://get.docker.com | sh
sudo usermod -aG docker $USER
# Step 2: Install the Dev Containers extension
code --install-extension ms-vscode-remote.remote-containers
# Step 3: Verify Docker is running
docker --version # Docker 24+ recommended
docker info # Verify Docker daemon is active
# Step 4: Open project — VS Code detects devcontainer.json
code /path/to/project
# Click "Reopen in Container" notification
# Or: Ctrl+Shift+P > "Dev Containers: Reopen in Container"
# Key commands (Command Palette):
# Dev Containers: Rebuild Container — after config changes
# Dev Containers: Rebuild Without Cache — force clean build
# Dev Containers: Reopen Folder Locally — exit container mode
# Dev Containers: Show Container Log — debug build issues
# Dev Containers: Open Folder in Container — open any folder
# Dev Containers: Clone Repository in Container Volume — faster I/OGitHub Codespaces Integration
GitHub Codespaces provides cloud-hosted dev containers that launch from any GitHub repository. It reads your devcontainer.json and builds the environment on a cloud VM, accessible via browser or local VS Code.
# Create a Codespace:
# 1. Go to repo on GitHub > Code > Codespaces > Create codespace
# Machine types (2026):
# 2-core / 8GB — free tier (120 hrs/month)
# 4-core / 16GB — \$0.36/hr
# 8-core / 32GB — \$0.72/hr
# 16-core / 64GB — \$1.44/hr
# 32-core / 128GB — \$2.88/hr (GPU available)
# Prebuilds: Settings > Codespaces > Set up prebuild
# Reduces Codespace startup to seconds
# Secrets: GitHub > Settings > Codespaces > Secrets
# Available as env vars, never committed to sourcePre-built Dev Container Images
Microsoft provides pre-built images at mcr.microsoft.com/devcontainers, optimized for dev container use with common tools and regular security updates.
| Image | Includes | Size |
|---|---|---|
| devcontainers/base:ubuntu | Git, curl, zsh, utilities | ~350MB |
| devcontainers/typescript-node:20 | Node.js 20, npm, yarn, TypeScript | ~650MB |
| devcontainers/python:3.12 | Python 3.12, pip, venv | ~600MB |
| devcontainers/go:1.22 | Go 1.22, gopls, delve | ~800MB |
| devcontainers/rust:1 | Rust, cargo, clippy | ~1.2GB |
| devcontainers/java:21 | JDK 21, Maven, Gradle | ~900MB |
| devcontainers/universal:2 | Node, Python, Java, Go, .NET, PHP, Ruby | ~6GB |
Custom Dockerfile for Dev Containers
When pre-built images do not cover your needs, provide a custom Dockerfile. The devcontainer.json references it and VS Code builds the image before starting the container.
When writing a Dockerfile for dev containers, start from a devcontainers base image whenever possible. These images already include common utilities like git, curl, sudo, and zsh. Then add your project-specific system packages, language runtimes, and global tools. Keep the image focused on development needs, not production requirements.
# .devcontainer/Dockerfile
FROM mcr.microsoft.com/devcontainers/base:ubuntu
# Install system dependencies for native compilation
RUN apt-get update && apt-get install -y \
build-essential \
libpq-dev \
libssl-dev \
pkg-config \
protobuf-compiler \
graphviz \
&& rm -rf /var/lib/apt/lists/*
# Install Node.js 20
RUN curl -fsSL https://deb.nodesource.com/setup_20.x | bash - \
&& apt-get install -y nodejs
# Install global npm packages
RUN npm install -g pnpm@9 turbo@2 tsx
# Install Rust toolchain (optional for projects using native modules)
RUN curl --proto "=https" --tlsv1.2 -sSf https://sh.rustup.rs | \
sh -s -- -y --default-toolchain stable
ENV PATH="/root/.cargo/bin:\${PATH}"
# Create non-root user workspace
WORKDIR /workspace// .devcontainer/devcontainer.json with custom Dockerfile
{
"name": "Custom Dev Environment",
"build": {
"dockerfile": "Dockerfile",
"context": "..",
"args": {
"NODE_VERSION": "20",
"VARIANT": "bookworm"
},
"cacheFrom": "ghcr.io/myorg/devcontainer:latest"
},
"remoteUser": "vscode",
"postCreateCommand": "npm install"
}Features System: Installing Tools and Languages
Dev Container Features are composable units of installation code. Instead of writing Dockerfile RUN commands, add Features to devcontainer.json. They handle installation, version pinning, and cross-platform compatibility.
Features are distributed as OCI artifacts (container images) and follow a well-defined installation protocol. Each Feature includes a devcontainer-feature.json manifest and an install.sh script. The official Features repository at github.com/devcontainers/features covers the most common tools, while community Features at github.com/devcontainers-contrib/features extend coverage to hundreds of additional tools.
// Popular Dev Container Features (2026)
{
"features": {
// Language runtimes
"ghcr.io/devcontainers/features/node:1": { "version": "20" },
"ghcr.io/devcontainers/features/python:1": { "version": "3.12" },
"ghcr.io/devcontainers/features/go:1": { "version": "1.22" },
"ghcr.io/devcontainers/features/rust:1": { "version": "stable" },
"ghcr.io/devcontainers/features/java:1": { "version": "21" },
// Container and orchestration tools
"ghcr.io/devcontainers/features/docker-in-docker:2": {},
"ghcr.io/devcontainers/features/docker-outside-of-docker:1": {},
"ghcr.io/devcontainers/features/kubectl-helm-minikube:1": {},
// Cloud CLIs
"ghcr.io/devcontainers/features/aws-cli:1": {},
"ghcr.io/devcontainers/features/azure-cli:1": {},
"ghcr.io/devcontainers/features/github-cli:1": {},
// Database clients
"ghcr.io/devcontainers/features/postgresql-client:1": {},
// Shell and utilities
"ghcr.io/devcontainers/features/common-utils:2": {
"installZsh": true,
"installOhMyZsh": true
},
"ghcr.io/devcontainers/features/sshd:1": {},
"ghcr.io/devcontainers/features/git-lfs:1": {}
}
}Docker Compose Multi-Service Setup
For applications needing databases, caches, or other services, Dev Containers integrate with Docker Compose. Define services in docker-compose.yml and reference it from devcontainer.json.
The key pattern is to use sleep infinity as the command for the main development service. This keeps the container running so VS Code can attach to it. The actual development server is started by the developer or by postStartCommand, not by Docker Compose directly. Other services like databases run their normal entrypoints.
# .devcontainer/docker-compose.yml
services:
app:
build:
context: ..
dockerfile: .devcontainer/Dockerfile
volumes:
- ..:/workspace:cached
- node-modules:/workspace/node_modules
command: sleep infinity
ports:
- "3000:3000"
- "9229:9229" # Node.js debugger
environment:
DATABASE_URL: postgresql://postgres:postgres@db:5432/myapp
REDIS_URL: redis://redis:6379
NODE_ENV: development
depends_on:
db:
condition: service_healthy
redis:
condition: service_started
db:
image: postgres:16-alpine
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: myapp
volumes:
- postgres-data:/var/lib/postgresql/data
- ./init.sql:/docker-entrypoint-initdb.d/init.sql
ports:
- "5432:5432"
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 5s
retries: 5
redis:
image: redis:7-alpine
ports:
- "6379:6379"
volumes:
- redis-data:/data
volumes:
node-modules:
postgres-data:
redis-data:// devcontainer.json with Docker Compose
{
"name": "Full Stack Dev",
"dockerComposeFile": "docker-compose.yml",
"service": "app",
"workspaceFolder": "/workspace",
"forwardPorts": [3000, 5432, 6379],
"postCreateCommand": "npm install && npx prisma migrate dev --name init",
"customizations": { "vscode": {
"extensions": ["prisma.prisma", "dbaeumer.vscode-eslint"]
}}
}Port Forwarding and Networking
Dev containers automatically forward ports from container to host. Configure forwarding behavior in devcontainer.json to control which ports are forwarded, their labels, and visibility.
{
"forwardPorts": [3000, 5432, 6379, 8080],
"portsAttributes": {
"3000": { "label": "Frontend", "onAutoForward": "openBrowser" },
"5432": { "label": "PostgreSQL", "onAutoForward": "silent" },
"6379": { "label": "Redis", "onAutoForward": "ignore" },
"8080": { "label": "API", "onAutoForward": "notify", "requireLocalPort": true }
},
"otherPortsAttributes": { "onAutoForward": "silent" }
}Environment Variables and Secrets
Dev containers support multiple ways to inject environment variables: inline in devcontainer.json, from .env files, or via GitHub Codespaces secrets for sensitive values.
// Method 1: Inline
{ "containerEnv": { "NODE_ENV": "development", "LOG_LEVEL": "debug" },
"remoteEnv": { "LOCAL_USER": "\${localEnv:USER}" } }
// Method 2: .env file (add to .gitignore!)
// { "runArgs": ["--env-file", ".devcontainer/.env"] }
// .devcontainer/.env:
// DATABASE_URL=postgresql://user:pass@db:5432/myapp
// AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE
// Method 3: GitHub Codespaces secrets
// GitHub > Settings > Codespaces > Secrets
// Available as env vars, never committedVS Code Extensions and Settings in devcontainer.json
Specify which VS Code extensions to install and what editor settings to apply, ensuring every developer has the same linter rules, formatter, and language support.
Extensions listed in the customizations block are installed inside the container, not on the host VS Code. This distinction matters because some extensions need access to language servers, runtimes, or tools that only exist inside the container. Extensions like ESLint need Node.js, Python extensions need the Python interpreter, and database extensions need database clients. By installing them in the container, they automatically have access to everything they need.
// VS Code customizations in devcontainer.json
{
"customizations": {
"vscode": {
"extensions": [
// Linting and formatting
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode",
"stylelint.vscode-stylelint",
// Language support
"ms-python.python",
"golang.go",
"rust-lang.rust-analyzer",
// Database and API tools
"prisma.prisma",
"humao.rest-client",
// Git and collaboration
"eamodio.gitlens",
"github.copilot",
// Testing
"vitest.explorer"
],
"settings": {
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.tabSize": 2,
"editor.rulers": [80, 120],
"typescript.preferences.importModuleSpecifier": "relative",
"files.trimTrailingWhitespace": true,
"files.insertFinalNewline": true
}
}
}
}Post-Create and Post-Start Commands
Lifecycle commands run at specific points during container setup, automating dependency installation, database seeding, or background service startup.
Understanding the lifecycle order is critical for efficient container setup. Commands that should only run once (like initial dependency installation) belong in postCreateCommand, while commands that should run on every restart (like database migrations) belong in postStartCommand. The object syntax allows running multiple commands in parallel, which speeds up container initialization significantly.
{
// Runs once after the container is first created
"postCreateCommand": "npm install && npx prisma generate",
// Runs every time the container starts
"postStartCommand": "npm run db:migrate",
// Runs every time VS Code attaches to the container
"postAttachCommand": "git fetch --all",
// Runs on rebuild to update dependencies incrementally
"updateContentCommand": "npm install",
// Parallel commands (object syntax — runs simultaneously):
"postCreateCommand": {
"deps": "npm install",
"db": "npx prisma migrate dev",
"seed": "npx prisma db seed",
"codegen": "npm run codegen"
},
// Runs on HOST before the container is built
"initializeCommand": "echo Starting dev container setup..."
}
// Complete lifecycle order:
// 1. initializeCommand — runs on HOST before build
// 2. onCreateCommand — once when container is created
// 3. updateContentCommand — on create and rebuild
// 4. postCreateCommand — once after container is created
// 5. postStartCommand — every time container starts
// 6. postAttachCommand — every time editor attachesVolumes and Mounts for Persistence
The workspace folder is bind-mounted by default. Add additional mounts for caches, credentials, or data that should persist across container rebuilds.
{
"mounts": [
"source=myproject-node-modules,target=/workspace/node_modules,type=volume",
"source=\${localEnv:HOME}/.ssh,target=/home/node/.ssh,type=bind,readonly",
"source=\${localEnv:HOME}/.gitconfig,target=/home/node/.gitconfig,type=bind,readonly",
"source=\${localEnv:HOME}/.aws,target=/home/node/.aws,type=bind,readonly",
"target=/tmp,type=tmpfs"
],
"workspaceMount": "source=\${localWorkspaceFolder},target=/workspace,type=bind,consistency=cached",
"workspaceFolder": "/workspace"
}GPU Support for ML/AI Development
Dev containers support GPU passthrough for ML/AI work. With NVIDIA Container Toolkit on the host, expose GPUs for training models or running CUDA kernels.
GPU support is particularly valuable for teams working on machine learning models, computer vision, natural language processing, or any CUDA-accelerated workload. The hostRequirements field with gpu set to optional allows the same devcontainer.json to work on both GPU and non-GPU machines, making it versatile across team members with different hardware.
// GPU-enabled dev container for ML/AI development
{
"image": "mcr.microsoft.com/devcontainers/python:3.12",
"features": {
"ghcr.io/devcontainers/features/nvidia-cuda:1": {
"installCudnn": true,
"cudaVersion": "12.4",
"cudnnVersion": "9"
},
"ghcr.io/devcontainers/features/python:1": { "version": "3.12" }
},
"runArgs": [
"--gpus", "all" // Pass all GPUs to container
// "--gpus", "device=0" // Or pass a specific GPU
],
"hostRequirements": {
"gpu": "optional" // Works without GPU too
},
"postCreateCommand": "pip install torch torchvision transformers datasets",
"customizations": {
"vscode": {
"extensions": [
"ms-python.python",
"ms-toolsai.jupyter",
"ms-toolsai.vscode-jupyter-slideshow"
]
}
}
}
// Host prerequisites:
// 1. NVIDIA driver installed on the host machine
// 2. Install NVIDIA Container Toolkit:
// sudo apt install nvidia-container-toolkit
// sudo systemctl restart docker
// 3. Verify setup:
// docker run --gpus all nvidia/cuda:12.4-base nvidia-smiMulti-Root Workspaces
Dev containers support multi-root workspaces where multiple repositories open in a single VS Code window, useful for microservice architectures.
{
"name": "Microservices Dev",
"dockerComposeFile": "docker-compose.yml",
"service": "workspace",
"workspaceFolder": "/workspaces",
"postCreateCommand": {
"frontend": "cd /workspaces && git clone https://github.com/org/frontend.git",
"backend": "cd /workspaces && git clone https://github.com/org/backend-api.git"
}
}
// Or use workspace file .devcontainer/workspace.code-workspace:
// { "folders": [
// { "path": "/workspaces/frontend", "name": "Frontend" },
// { "path": "/workspaces/backend-api", "name": "Backend" }
// ]}Dev Container Templates and Community Features
The specification includes a Templates system providing starter configs for common scenarios, available from official and community repositories.
# Install devcontainer CLI
npm install -g @devcontainers/cli
# Apply a template:
devcontainer templates apply \
--template-id ghcr.io/devcontainers/templates/typescript-node
# Or via VS Code: Dev Containers: Add Dev Container Configuration Files...
# Community Features (add to "features" in devcontainer.json):
# ghcr.io/devcontainers-contrib/features/bun:1
# ghcr.io/devcontainers-contrib/features/pnpm:2
# ghcr.io/devcontainers-contrib/features/turbo-cli:1
# ghcr.io/devcontainers-contrib/features/act:1 (GitHub Actions local)
# ghcr.io/devcontainers-contrib/features/mkcert:1 (local HTTPS)JetBrains Dev Container Support
JetBrains IDEs support Dev Containers through the remote development gateway. The IDE reads devcontainer.json, builds the container, and connects via SSH with the full IDE backend inside.
{
"customizations": {
"jetbrains": {
"backend": {
"productCode": "IU", // IntelliJ IDEA Ultimate
// "PS" PhpStorm, "WS" WebStorm, "PY" PyCharm, "GO" GoLand, "CL" CLion
"plugins": ["org.jetbrains.plugins.go", "com.intellij.database"]
}
},
"vscode": { "extensions": ["dbaeumer.vscode-eslint"] }
}
}
// Steps: Install JetBrains Gateway > Select "Dev Containers"
// > Choose project > Gateway builds container and connects IDEPerformance Optimization
Dev containers can feel slow if misconfigured, especially on macOS where Docker runs in a Linux VM. Here are the most impactful optimizations.
- Use named Docker volumes for node_modules and dependency directories instead of bind mounts
- Enable Docker BuildKit for faster builds with better layer caching
- Use pre-built images instead of building from Dockerfile every time
- Configure Codespaces prebuilds to pre-compute your dev container image
- Mount .gitconfig and SSH keys from host instead of regenerating inside the container
- Set updateContentCommand for incremental dependency updates on rebuild
- Use the cacheFrom property to leverage Docker registry layer caching
// Performance-optimized devcontainer.json
{
"image": "mcr.microsoft.com/devcontainers/typescript-node:20",
"mounts": ["source=proj-nm,target=/workspace/node_modules,type=volume"],
"workspaceMount": "source=\${localWorkspaceFolder},target=/workspace,type=bind,consistency=cached",
"workspaceFolder": "/workspace",
"cacheFrom": "ghcr.io/myorg/devcontainer-cache:latest",
"updateContentCommand": "npm install",
"runArgs": ["--memory=8g", "--cpus=4"],
"hostRequirements": { "cpus": 4, "memory": "8gb", "storage": "32gb" }
}Team Onboarding with Dev Containers
Dev containers transform onboarding from a multi-day ordeal into a single-click operation. Combined with GitHub Codespaces, onboarding requires zero local setup.
The traditional onboarding process involves installing the correct versions of multiple language runtimes, setting up databases, configuring environment variables, installing editor plugins, and troubleshooting platform-specific issues. With Dev Containers, all of this is encoded in the repository. The onboarding documentation reduces from pages of instructions to a single step: open the project in VS Code and accept the container prompt.
# Option A: Local development (VS Code)
# 1. Install Docker Desktop and VS Code
# 2. Install Dev Containers extension
# 3. Clone the repository and open in VS Code:
git clone https://github.com/myorg/myproject.git
code myproject
# 4. Click "Reopen in Container" when prompted
# 5. Done — full environment ready in 2-5 minutes
# Option B: Cloud development (GitHub Codespaces)
# 1. Go to github.com/myorg/myproject
# 2. Click Code > Codespaces > Create codespace
# 3. Done — browser-based VS Code, zero local install needed
# What devcontainer.json provides automatically:
# - Correct Node.js / Python / Go / Rust version
# - All required VS Code extensions pre-installed
# - Database (PostgreSQL) running and seeded
# - Redis cache running
# - Environment variables configured
# - Git hooks and linters configured
# - All dependencies installed (npm/pip/cargo)
# - Ports forwarded for local testing
# - Shared editor settings (formatting, tab size, rulers)CI/CD with Dev Containers
The devcontainer CLI and GitHub Actions let you build and test inside the exact same environment developers use locally, eliminating CI/CD environment drift.
The devcontainers/ci GitHub Action handles building the dev container image, starting the container, running your commands inside it, and optionally pushing the built image to a container registry for caching. This means your CI pipeline uses the exact same Node.js version, Python packages, system libraries, and tooling that developers use locally. No more debugging CI-only failures caused by environment differences.
# .github/workflows/ci.yml — Dev Container CI/CD
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
build-and-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build and run dev container
uses: devcontainers/ci@v0.3
with:
# Run all checks inside the dev container
runCmd: |
npm run lint
npm run type-check
npm run test
npm run build
# Cache the built image in a container registry
imageName: ghcr.io/myorg/devcontainer-cache
cacheFrom: ghcr.io/myorg/devcontainer-cache
push: filter
refFilterForPush: refs/heads/main
# Standalone CLI usage (local or any CI system):
# npm install -g @devcontainers/cli
# devcontainer build --workspace-folder .
# devcontainer up --workspace-folder .
# devcontainer exec --workspace-folder . npm test
# devcontainer exec --workspace-folder . npm run buildSecurity Best Practices
Dev containers run arbitrary code on your machine, so security matters. Follow these practices.
- Never run containers as root — use the remoteUser field
- Pin image versions with SHA digests in critical environments
- Store secrets in Codespaces secrets or local env vars, never in devcontainer.json
- Audit third-party Features before adding them
- Use Docker Content Trust to verify image signatures
- Avoid --privileged unless absolutely necessary
- Keep base images updated with regular rebuilds
- Use .env files in .gitignore to prevent secret commits
Common Patterns by Language
Production-ready devcontainer.json examples for the most popular languages in 2026.
Node.js / TypeScript
{
"image": "mcr.microsoft.com/devcontainers/typescript-node:20",
"features": { "ghcr.io/devcontainers-contrib/features/pnpm:2": {} },
"forwardPorts": [3000, 5173],
"postCreateCommand": "pnpm install",
"mounts": ["source=nm,target=/workspace/node_modules,type=volume"],
"customizations": { "vscode": {
"extensions": ["dbaeumer.vscode-eslint", "esbenp.prettier-vscode"]
}}
}Python
{
"image": "mcr.microsoft.com/devcontainers/python:3.12",
"forwardPorts": [8000],
"postCreateCommand": "pip install -r requirements.txt",
"customizations": { "vscode": {
"extensions": ["ms-python.python", "charliermarsh.ruff"],
"settings": { "python.defaultInterpreterPath": "/usr/local/bin/python" }
}}
}Go
{
"image": "mcr.microsoft.com/devcontainers/go:1.22",
"features": { "ghcr.io/devcontainers/features/docker-in-docker:2": {} },
"forwardPorts": [8080],
"postCreateCommand": "go mod download",
"mounts": ["source=gomod,target=/go/pkg/mod,type=volume"],
"customizations": { "vscode": { "extensions": ["golang.go"] }}
}Rust
{
"image": "mcr.microsoft.com/devcontainers/rust:1",
"forwardPorts": [8080],
"postCreateCommand": "cargo build",
"mounts": [
"source=cargo-reg,target=/usr/local/cargo/registry,type=volume",
"source=cargo-target,target=/workspace/target,type=volume"
],
"customizations": { "vscode": {
"extensions": ["rust-lang.rust-analyzer", "vadimcn.vscode-lldb"],
"settings": { "rust-analyzer.checkOnSave.command": "clippy" }
}}
}Java
{
"image": "mcr.microsoft.com/devcontainers/java:21",
"features": { "ghcr.io/devcontainers/features/java:1": {
"version": "21", "installMaven": true, "installGradle": true } },
"forwardPorts": [8080, 5005],
"postCreateCommand": "./mvnw dependency:resolve",
"mounts": ["source=m2,target=/home/vscode/.m2/repository,type=volume"],
"customizations": { "vscode": {
"extensions": ["vscjava.vscode-java-pack", "vmware.vscode-spring-boot"]
}}
}Troubleshooting Guide
Here are the most common Dev Container issues and solutions.
| Issue | Cause | Solution |
|---|---|---|
| Container fails to build | Dockerfile error or missing image | Check Container Log via Command Palette. Verify image name. |
| Slow file I/O on macOS | Bind mounts through Linux VM | Use named volumes for node_modules. Set consistency: cached. |
| Port already in use | Host port conflict | Stop conflicting service or change port in devcontainer.json. |
| Extensions not installing | Platform/architecture mismatch | Check extension supports Linux containers. |
| Git SSH auth fails | SSH agent not forwarded | Run ssh-add on host. VS Code forwards agent automatically. |
| Out of disk space | Docker images and cache | Run docker system prune. Increase Docker disk allocation. |
| Features fail to install | Network issues or version conflict | Check connectivity. Pin Feature versions. |
| Container keeps restarting | postStartCommand exits with error | Check terminal output. Use background execution for long commands. |
# Common troubleshooting commands:
# View dev container build logs
# VS Code: Command Palette > Dev Containers: Show Container Log
# List running dev containers
docker ps --filter "label=devcontainer.local_folder"
# Inspect container configuration
docker inspect <container-id> | jq ".[0].Config"
# Check Docker resource usage
docker system df
# Clean up unused images, containers, and volumes
docker system prune -a --volumes
# Force rebuild without cache
# VS Code: Command Palette > Dev Containers: Rebuild Without Cache
# Test devcontainer.json validity with CLI:
npm install -g @devcontainers/cli
devcontainer build --workspace-folder .
devcontainer up --workspace-folder .
# Check container logs for errors:
docker logs <container-id> --tail 100
# Enter the container manually for debugging:
docker exec -it <container-id> /bin/bashFrequently Asked Questions
What is the difference between Dev Containers and Docker Compose?
Docker Compose defines multi-container stacks, while Dev Containers define development environments with editor settings, extensions, and tools on top. Dev Containers can use Docker Compose under the hood for multi-service setups.
Do Dev Containers work without VS Code?
Yes. JetBrains IDEs support devcontainer.json natively. The devcontainer CLI works from any terminal. DevPod and GitHub Codespaces also support the specification.
How do Dev Containers affect build performance?
Initial build can take minutes depending on image and Features. Subsequent starts are fast due to Docker layer caching. On macOS, use named volumes for dependencies to avoid file system bottlenecks.
Can I use Dev Containers for monorepos?
Yes. Place devcontainer.json at the repo root. For multi-repo projects, use Docker Compose to define all services and attach VS Code to the primary service.
How do I share SSH keys and Git config with the container?
VS Code automatically forwards your local SSH agent. Git credentials are forwarded via the built-in credential helper. For additional credentials, use mounts or environment variables.
Are Dev Containers free?
The specification, VS Code, and extension are free. Local containers use your Docker installation at no cost. GitHub Codespaces has a free tier (120 core-hours/month) with paid tiers for heavier usage.
Can Dev Containers access the host file system?
The workspace folder is mounted by default. Add mounts in devcontainer.json for other directories. Ports are forwarded between container and host automatically.
How do I update a Dev Container after changes?
Run Dev Containers: Rebuild Container in VS Code. For clean rebuilds, use Rebuild Without Cache. In Codespaces, rebuild from the command palette or create a new Codespace.
Conclusion
Dev Containers are the most practical solution to reproducible development environments in 2026. By defining your setup as a devcontainer.json committed to your repository, you ensure every team member, CI pipeline, and cloud workspace runs the identical environment. Start with a pre-built image, add Features for your tools, and iterate from there.
Getting Started Checklist
- Install Docker Desktop and VS Code with the Dev Containers extension
- Create a .devcontainer folder in your repository root
- Add a devcontainer.json with a pre-built image matching your language
- Add Features for additional tools (Docker-in-Docker, AWS CLI, database clients)
- Configure VS Code extensions and settings in the customizations block
- Set up lifecycle commands for dependency installation and service startup
- Add Docker Compose if you need databases or other services
- Use named volumes for dependency directories to improve macOS performance
- Configure GitHub Codespaces prebuilds for instant cloud environments
- Set up CI/CD with the devcontainer CLI or GitHub Actions integration
For additional resources, visit the official Dev Container specification at containers.dev, explore the pre-built images and Features at github.com/devcontainers, and check the VS Code remote development documentation. The ecosystem is growing rapidly with community Features and Templates covering nearly every development scenario.