DevContainers: Consistent Development Environments Across Teams

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:

  1. Open in Container: Ctrl+Shift+P → "Dev Containers: Open Folder in Container"
  2. Rebuild Container: Ctrl+Shift+P → "Dev Containers: Rebuild Container"
  3. 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

  1. Version Control Everything: Commit all .devcontainer files to repository
  2. Use Official Images: Prefer mcr.microsoft.com/devcontainers/* images
  3. Minimal Base Images: Start with minimal images, add only required tools
  4. Non-Root User: Always specify remoteUser for security
  5. Port Forwarding: Document all forwarded ports in README
  6. Lifecycle Scripts: Use postCreateCommand for one-time setup only
  7. 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


Build once, develop anywhere.