Mastering Dev Containers in VS Code
Create consistent, portable, and reproducible development environments using Dev Containers
Developers often face the “works on my machine” dilemma due to dependency mismatches, tool versions, or OS differences. Dev Containers in Visual Studio Code (VS Code) solve this elegantly β by letting you develop inside a containerized environment configured specifically for your project.
Modern software development demands consistent, reproducible environments that work across machines and operating systems. Whether you’re working on a Python data science project, a Node.js web application, or a Go microservice, ensuring that every team member has an identical development setup can be challenging.
This comprehensive guide walks through what Dev Containers are, why they’re valuable, and how to set them up in VS Code for smooth, portable development workflows. You’ll learn everything from basic setup to advanced configurations with Docker Compose and best practices for team collaboration.
π§© What Are Dev Containers?
Dev Containers are a feature provided by the VS Code Remote - Containers extension (now part of VS Code Remote Development). They allow you to open your project in a Docker container that’s pre-configured with all your dependencies, languages, and tools.
Think of it as:
“A fully configured development environment, defined as code.”
Instead of installing Python, Node.js, databases, and various tools directly on your machine, you define them in configuration files. When you open the project in VS Code, it automatically spins up a container with everything pre-installed and configured exactly as specified.
A Dev Container setup typically includes:
- A Dockerfile or reference to a base image (defining the container OS, languages, and tools)
- A
devcontainer.json
file (configuring workspace settings, VS Code extensions, port forwarding, environment variables, and startup commands) - Optional docker-compose.yml if your project depends on multiple services (like databases, Redis, message queues, etc.)
βοΈ Why Use Dev Containers?
Here’s what makes them powerful:
-
Reproducibility: Every developer and CI system uses the exact same environment. No more “it works on my machine but not on yours” issues. What runs on your laptop will run identically on your colleague’s Windows machine, Mac, or Linux workstation.
-
Isolation: No need to pollute your local machine with conflicting dependencies. Work on multiple projects that require different versions of Python, Node.js, or other tools without version conflicts or virtual environment juggling.
-
Portability: Works on any OS that supports Docker. Your development environment travels with your code. Clone a repository, open it in VS Code, and you’re ready to code in minutes β regardless of your operating system.
-
Team Consistency: One configuration shared across your entire team. New team members can get up and running in minutes instead of spending hours (or days) configuring their development environment with the right tools and versions.
-
Automation: Automatically installs VS Code extensions, language dependencies, and tools when you open the project. Post-create commands can run database migrations, seed data, or perform other setup tasks without manual intervention.
-
Security: Isolate potentially risky dependencies in containers. If you need to test with an older, vulnerable version of a library, it stays contained and doesn’t affect your host system.
Real-world example: Imagine joining a team working on a microservices project that uses Python 3.11, PostgreSQL 15, Redis, and Elasticsearch. Without Dev Containers, you’d spend hours installing and configuring each component. With Dev Containers, you open the project in VS Code, let it build the container, and you’re writing code within 5-10 minutes.
π§± Setting Up a Dev Container in VS Code
Letβs go step-by-step.
1. Install the Required Tools
Before you start, ensure you have the following installed:
-
Docker Desktop (or an equivalent container runtime like Podman)
- For Windows/Mac: Download and install Docker Desktop
- For Linux: Install Docker Engine and ensure your user is in the docker group
-
VS Code (latest version recommended)
-
The Dev Containers extension (by Microsoft)
- Open VS Code
- Go to Extensions (
Ctrl+Shift+X
orCmd+Shift+X
) - Search for “Dev Containers”
- Install the extension with ID:
ms-vscode-remote.remote-containers
Verify your setup:
# Check Docker is running
docker --version
docker ps
# Should output Docker version and running containers (if any)
2. Initialize the Dev Container
Open your project folder in VS Code
and open the Command Palette (Ctrl+Shift+P
or Cmd+Shift+P
on macOS), then type and select:
Dev Containers: Add Dev Container Configuration Files...
VS Code will present a list of predefined environment templates. Choose the one that matches your project:
- Node.js β JavaScript/TypeScript projects
- Python β Data science, web apps, scripts
- Go β Go applications and services
- .NET β C#/F# applications
- Java β Spring Boot, Maven, Gradle projects
- Docker-in-Docker β When you need Docker inside your container
- And many more…
You can also select additional features like:
- Common utilities (git, curl, wget)
- Database clients
- Cloud CLI tools (AWS, Azure, GCP)
This wizard creates a .devcontainer
folder with:
devcontainer.json
β Main configuration fileDockerfile
β Custom image definition (or a reference to a pre-built base image)
3. Customize devcontainer.json
The devcontainer.json
file is where the magic happens. Here’s a well-documented example for a Node.js project:
{
// Container display name in VS Code
"name": "Node.js Development Container",
// Build configuration - can use Dockerfile or pre-built image
"build": {
"dockerfile": "Dockerfile",
"context": ".."
},
// Alternative: use a pre-built image instead of Dockerfile
// "image": "mcr.microsoft.com/devcontainers/javascript-node:18",
// Workspace configuration
"customizations": {
"vscode": {
// VS Code settings that apply in the container
"settings": {
"terminal.integrated.defaultProfile.linux": "bash",
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
// Extensions to install automatically
"extensions": [
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode",
"eamodio.gitlens",
"ms-azuretools.vscode-docker"
]
}
},
// Port forwarding - make container ports available on host
"forwardPorts": [3000, 5432],
"portsAttributes": {
"3000": {
"label": "Application",
"onAutoForward": "notify"
}
},
// Commands to run at different stages
"postCreateCommand": "npm install", // After container is created
"postStartCommand": "npm run dev", // After container starts
// Environment variables
"containerEnv": {
"NODE_ENV": "development",
"PORT": "3000"
},
// Run container as non-root user (recommended for security)
"remoteUser": "node",
// Mount additional volumes
"mounts": [
"source=${localEnv:HOME}/.ssh,target=/home/node/.ssh,readonly,type=bind"
]
}
Key configuration options explained:
name
β Display name shown in VS Code status barbuild
/image
β Use a Dockerfile or pre-built imagecustomizations.vscode.extensions
β VS Code extensions to auto-installforwardPorts
β Ports to expose from container to hostpostCreateCommand
β Runs once when container is first created (e.g., install dependencies)postStartCommand
β Runs every time the container startscontainerEnv
β Environment variables available in the containerremoteUser
β User account to use inside the containermounts
β Additional files/folders to mount (like SSH keys)
π‘ Pro Tips:
- Use
postCreateCommand
for slow operations (npm install, pip install) - Use
postStartCommand
for fast startup tasks (database migrations) - Always specify extensions your project needs β this ensures consistent tooling
- Use environment variables for configuration that differs between developers
4. Build and Open in Container
Once your configuration is ready, it’s time to launch your development environment:
Open Command Palette (Ctrl+Shift+P
/ Cmd+Shift+P
) and run:
Dev Containers: Reopen in Container
What happens next:
-
Image Building β VS Code builds the Docker image based on your Dockerfile or pulls a pre-built image. This may take a few minutes the first time.
-
Container Creation β Docker creates a new container from the built image.
-
Volume Mounting β Your project directory is mounted into the container, making your code accessible inside.
-
Extensions Installation β All specified VS Code extensions are automatically installed in the container.
-
Post-Create Commands β Your
postCreateCommand
runs (e.g.,npm install
,pip install -r requirements.txt
). -
Ready! β VS Code reconnects to the container, and you’re now developing inside it.
Verify you’re in the container:
You can confirm you’re working inside the container by opening a terminal and running:
# Check the operating system
uname -a
# Output: Linux ... (container's kernel)
# Check hostname (usually the container ID)
hostname
# Output: abc123def456
# Check running processes
ps aux
# You'll see container processes, not your host system's
Notice the VS Code status bar (bottom-left) now shows: Dev Container: [Your Container Name]
Container lifecycle commands:
- Rebuild Container β
Dev Containers: Rebuild Container
(when you change Dockerfile) - Rebuild Without Cache β
Dev Containers: Rebuild Container Without Cache
(for fresh build) - Reopen Locally β
Dev Containers: Reopen Folder Locally
(exit container, work on host)
5. Add Additional Services (Optional)
Real-world applications often depend on databases, caching layers, message queues, or other services. You can use Docker Compose to orchestrate multiple containers.
Example: Full-stack application with Node.js, PostgreSQL, and Redis
Create a docker-compose.yml
in your .devcontainer
folder:
version: "3.8"
services:
# Main development container
app:
build:
context: ..
dockerfile: .devcontainer/Dockerfile
volumes:
# Mount project directory
- ..:/workspace:cached
# Use named volume for node_modules (better performance)
- node_modules:/workspace/node_modules
# Keep container running
command: sleep infinity
# Network access to other services
depends_on:
- db
- redis
environment:
DATABASE_URL: postgresql://dev:secret@db:5432/appdb
REDIS_URL: redis://redis:6379
# PostgreSQL database
db:
image: postgres:15-alpine
restart: unless-stopped
volumes:
- postgres-data:/var/lib/postgresql/data
environment:
POSTGRES_USER: dev
POSTGRES_PASSWORD: secret
POSTGRES_DB: appdb
ports:
- "5432:5432"
# Redis cache
redis:
image: redis:7-alpine
restart: unless-stopped
volumes:
- redis-data:/data
ports:
- "6379:6379"
volumes:
postgres-data:
redis-data:
node_modules:
Then, update your devcontainer.json
to use Docker Compose:
{
"name": "Full-stack Dev Environment",
// Use docker-compose instead of single container
"dockerComposeFile": "docker-compose.yml",
// Which service to use as the development container
"service": "app",
// Path to workspace folder inside container
"workspaceFolder": "/workspace",
"customizations": {
"vscode": {
"extensions": [
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode",
"ms-azuretools.vscode-docker",
"ckolkman.vscode-postgres" // PostgreSQL client
]
}
},
"forwardPorts": [3000, 5432, 6379],
"postCreateCommand": "npm install && npm run db:migrate",
"remoteUser": "node"
}
What this setup provides:
app
β Your development container with Node.jsdb
β PostgreSQL database, accessible atdb:5432
from your appredis
β Redis cache, accessible atredis:6379
- Named volumes β Persist database data between container restarts
- Port forwarding β Access all services from your host machine
Connect to services from your code:
// In your Node.js application
const { Pool } = require('pg');
const redis = require('redis');
// PostgreSQL connection
const pool = new Pool({
connectionString: process.env.DATABASE_URL
// Resolves to: postgresql://dev:secret@db:5432/appdb
});
// Redis connection
const redisClient = redis.createClient({
url: process.env.REDIS_URL
// Resolves to: redis://redis:6379
});
Access services from your host:
- App:
http://localhost:3000
- PostgreSQL:
localhost:5432
(using any PostgreSQL client) - Redis:
localhost:6379
(usingredis-cli
or GUI tools)
Now, when you open the project in VS Code, all services start together automatically!
π§ Advanced Tips and Best Practices
Use Pre-Built Images
Save significant build time by starting from Microsoft’s official devcontainer images:
{
"image": "mcr.microsoft.com/devcontainers/python:3.11",
"features": {
"ghcr.io/devcontainers/features/git:1": {},
"ghcr.io/devcontainers/features/github-cli:1": {}
}
}
Features are reusable installation scripts for common tools (Git, GitHub CLI, Node, AWS CLI, etc.).
Version Control Best Practices
Always commit your .devcontainer
folder:
git add .devcontainer/
git commit -m "Add Dev Container configuration"
git push
This ensures:
- β New team members get the environment automatically
- β Environment changes are tracked and reviewable
- β Everyone develops in the same setup
Pro tip: Add a README section explaining the dev container setup:
## Development Setup
This project uses VS Code Dev Containers. To get started:
1. Install Docker Desktop and VS Code
2. Install the "Dev Containers" extension
3. Clone this repository
4. Open in VS Code
5. Click "Reopen in Container" when prompted
Debugging in Containers
Debugging works seamlessly. Configure your launch.json
as usual:
{
"version": "0.2.0",
"configurations": [
{
"name": "Launch Node.js",
"type": "node",
"request": "launch",
"program": "${workspaceFolder}/index.js",
"skipFiles": ["<node_internals>/**"]
}
]
}
Set breakpoints and debug normally β VS Code handles the container connection automatically.
Continuous Integration Parity
Use the same container image in your CI/CD pipeline:
# GitHub Actions example
name: CI
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
container:
image: mcr.microsoft.com/devcontainers/javascript-node:18
steps:
- uses: actions/checkout@v3
- run: npm install
- run: npm test
This ensures dev/prod parity β if tests pass locally, they’ll pass in CI.
Performance Optimization
For macOS/Windows users β use named volumes for dependencies:
{
"mounts": [
"source=myproject-node_modules,target=${containerWorkspaceFolder}/node_modules,type=volume"
]
}
This significantly improves file I/O performance for node_modules
, venv
, etc.
Multi-Stage Development
Create different configurations for different team roles:
.devcontainer/
βββ devcontainer.json # Default (full-stack)
βββ frontend/
β βββ devcontainer.json # Frontend-only (lighter)
βββ backend/
βββ devcontainer.json # Backend-only (with DB)
Team members can choose their environment when opening the project.
Working with SSH Keys and Git
Mount your SSH keys for Git operations:
{
"mounts": [
"source=${localEnv:HOME}${localEnv:USERPROFILE}/.ssh,target=/home/node/.ssh,readonly,type=bind"
],
"postCreateCommand": "ssh-add ~/.ssh/id_ed25519 || true"
}
Custom Environment Files
Load environment-specific configuration:
{
"runArgs": ["--env-file", ".devcontainer/.env"]
}
.devcontainer/.env
:
API_KEY=dev_key_here
DEBUG=true
LOG_LEVEL=debug
π§ Common Troubleshooting
Container Won’t Start
Error: Cannot connect to the Docker daemon
Solution:
- Ensure Docker Desktop is running
- On Linux, check:
sudo systemctl status docker
- Verify Docker is in your PATH:
docker --version
Slow Performance on macOS/Windows
Issue: File operations are slow
Solutions:
-
Use named volumes for
node_modules
,venv
, etc. -
Enable file sharing in Docker Desktop settings
-
Consider using
cached
ordelegated
mount options:"workspaceMount": "source=${localWorkspaceFolder},target=/workspace,type=bind,consistency=cached"
Extensions Not Installing
Issue: Extensions specified in devcontainer.json
don’t install
Solutions:
- Rebuild container:
Dev Containers: Rebuild Container
- Check extension IDs are correct
- Ensure extensions support remote containers (most do)
Port Already in Use
Error: Port 3000 is already allocated
Solutions:
- Stop conflicting containers:
docker ps
anddocker stop <container>
- Change port mapping in
forwardPorts
- Use dynamic ports: VS Code will auto-assign available ports
Changes to Dockerfile Not Applied
Issue: Modified Dockerfile but changes don’t appear
Solution: Rebuild without cache:
Dev Containers: Rebuild Container Without Cache
Container Exits Immediately
Issue: Container starts then stops
Solution: Add a command to keep it running in docker-compose.yml
:
command: sleep infinity
Or in devcontainer.json
:
{
"overrideCommand": true
}
β Conclusion
Dev Containers in VS Code bring consistency, simplicity, and automation to your development workflow. They turn complex, fragile setups into code-defined environments that just work, regardless of your machine or operating system.
Key takeaways:
- π― Eliminate “works on my machine” problems β Everyone uses identical environments
- π Faster onboarding β New team members productive in minutes, not days
- π Better security β Isolate dependencies from your host system
- π¦ Portable β Your environment travels with your code
- π€ Team consistency β No more dependency version conflicts
- π CI/CD parity β Use the same image in development and continuous integration
Whether you’re working on a simple Python script or a complex microservices architecture with multiple databases, Dev Containers provide a robust foundation for modern development.
If you collaborate on multi-language projects, contribute to open-source repositories, onboard new developers frequently, or simply want clean and reproducible dev environments β Dev Containers are a must-have tool in your stack.
Start small: try Dev Containers on your next project. Once you experience the benefits, you’ll wonder how you ever developed without them.
π Useful Resources and Related Articles
Official Documentation:
- Microsoft Dev Containers Documentation
- Dev Container Images Repository β Pre-built images for various languages and frameworks
- Dev Container Features β Reusable dev container configuration snippets
Related Articles on This Site:
- VSCode Cheatsheet β Essential VS Code shortcuts and commands
- Docker Cheatsheet β Docker commands reference
- Docker Compose Cheatsheet β Multi-container orchestration
- Python Cheatsheet β Python language reference
- Install Node.js β Node.js installation guide
- Go Cheatsheet β Go language reference
- Programming Languages and Frameworks Popularity β Technology trends and rankings