Dev containers com Docker + VS Code: ambiente reproduzível do zero

1. Fundamentos dos Dev Containers

Dev Containers representam uma evolução no desenvolvimento de software, combinando o isolamento do Docker com a flexibilidade do VS Code. O conceito central é simples: todo o ambiente de desenvolvimento — incluindo runtime, ferramentas, extensões e configurações — roda dentro de um container Docker, garantindo que cada desenvolvedor da equipe trabalhe exatamente no mesmo ambiente.

A diferença fundamental entre usar Docker tradicional e Dev Containers está na experiência do desenvolvedor. No Docker tradicional, você gerencia containers manualmente via terminal, monta volumes e configura redes. Com Dev Containers, o VS Code gerencia todo esse ciclo automaticamente: ao abrir uma pasta, ele detecta a configuração, constrói a imagem, inicia o container e conecta o editor diretamente ao ambiente isolado.

O maior benefício prático é a eliminação do clássico "funciona na minha máquina". Se o ambiente está versionado no repositório, qualquer pessoa com Docker e VS Code pode começar a contribuir em minutos, sem instalar nada além das ferramentas base.

2. Configuração inicial do ambiente

Para começar, você precisa de dois pré-requisitos:
- Docker Desktop (ou Docker Engine no Linux)
- VS Code com a extensão Dev Containers (ms-vscode-remote.remote-containers)

Crie a estrutura básica do projeto:

meu-projeto/
├── .devcontainer/
│   └── devcontainer.json
├── src/
└── README.md

O arquivo .devcontainer/devcontainer.json é o coração da configuração. Um exemplo mínimo para Node.js 20:

{
    "name": "Node.js Dev Container",
    "image": "mcr.microsoft.com/devcontainers/javascript-node:20",
    "customizations": {
        "vscode": {
            "extensions": [
                "dbaeumer.vscode-eslint",
                "esbenp.prettier-vscode"
            ],
            "settings": {
                "editor.formatOnSave": true
            }
        }
    },
    "postCreateCommand": "npm install"
}

3. Personalização da imagem com Dockerfile

Para projetos com dependências específicas, crie um Dockerfile dentro de .devcontainer/ e referencie-o no devcontainer.json:

# .devcontainer/Dockerfile
FROM mcr.microsoft.com/devcontainers/javascript-node:20

# Instalar ferramentas adicionais
RUN apt-get update && apt-get install -y \
    postgresql-client \
    redis-tools \
    && rm -rf /var/lib/apt/lists/*

# Instalar dependências globais do Node
RUN npm install -g nodemon typescript

# Definir diretório de trabalho
WORKDIR /workspace

No devcontainer.json, substitua "image" por "build":

{
    "name": "Projeto Customizado",
    "build": {
        "dockerfile": "Dockerfile"
    },
    // ... demais configurações
}

Boas práticas para o Dockerfile:
- Use imagens oficiais da Microsoft (mcr.microsoft.com/devcontainers/...) como base
- Agrupe comandos RUN para reduzir camadas
- Limpe o cache do apt-get na mesma camada da instalação

4. Gerenciamento de extensões e configurações do VS Code

Declare extensões obrigatórias no devcontainer.json para que todos usem as mesmas ferramentas:

"extensions": [
    "dbaeumer.vscode-eslint",
    "esbenp.prettier-vscode",
    "ms-azuretools.vscode-docker",
    "github.copilot",
    "eamodio.gitlens"
]

Para configurações do editor, use o campo settings:

"settings": {
    "editor.defaultFormatter": "esbenp.prettier-vscode",
    "editor.formatOnSave": true,
    "files.autoSave": "onFocusChange",
    "typescript.updateImportsOnFileMove.enabled": "always"
}

Crie também um .vscode/tasks.json para tarefas de build e teste:

{
    "version": "2.0.0",
    "tasks": [
        {
            "label": "Build",
            "type": "shell",
            "command": "npm run build",
            "group": "build"
        },
        {
            "label": "Test",
            "type": "shell",
            "command": "npm test",
            "group": "test"
        }
    ]
}

5. Integração com ferramentas de produtividade

Para projetos que dependem de serviços como banco de dados, crie um docker-compose.yml dentro de .devcontainer/:

version: '3.8'
services:
  app:
    build: 
      context: .
      dockerfile: Dockerfile
    volumes:
      - ..:/workspace:cached
      - node_modules:/workspace/node_modules
    command: sleep infinity
    ports:
      - "3000:3000"
    depends_on:
      - db
      - redis

  db:
    image: postgres:16
    environment:
      POSTGRES_USER: app
      POSTGRES_PASSWORD: app123
      POSTGRES_DB: appdb
    volumes:
      - postgres_data:/var/lib/postgresql/data

  redis:
    image: redis:7-alpine
    volumes:
      - redis_data:/data

volumes:
  postgres_data:
  redis_data:
  node_modules:

No devcontainer.json, referencie o compose:

{
    "name": "Projeto com Serviços",
    "dockerComposeFile": "docker-compose.yml",
    "service": "app",
    "workspaceFolder": "/workspace",
    "postCreateCommand": "npm install && npx prisma generate"
}

Os volumes são essenciais para persistir dados do banco e cache de dependências (node_modules) entre rebuilds.

6. Fluxo de trabalho no dia a dia

Para abrir um projeto existente em Dev Container:

  1. No VS Code, pressione F1 e execute Dev Containers: Reopen in Container
  2. Ou clique no ícone verde no canto inferior esquerdo e selecione a opção

Comandos essenciais:
- Rebuild Container: Dev Containers: Rebuild Container — reconstrói a imagem do zero
- Reopen in Container: reconecta sem rebuild
- Dev Containers: Attach to Running Container: conecta a um container já em execução

Para depuração (debug), configure um launch.json dentro de .vscode/:

{
    "version": "0.2.0",
    "configurations": [
        {
            "type": "node",
            "request": "launch",
            "name": "Debug App",
            "runtimeExecutable": "npm",
            "runtimeArgs": ["run", "dev"],
            "port": 9229,
            "restart": true
        }
    ]
}

O VS Code mapeia automaticamente as portas do container para sua máquina local, permitindo testar APIs e interfaces web normalmente.

7. Compartilhamento e versionamento do ambiente

Versionar a pasta .devcontainer no Git é obrigatório:

git add .devcontainer/
git commit -m "Adiciona configuração Dev Container"

No README do projeto, inclua instruções mínimas:

# Meu Projeto

## Setup

1. Instale Docker Desktop e VS Code com extensão Dev Containers
2. Clone o repositório
3. Abra a pasta no VS Code
4. Clique em "Reopen in Container" (canto inferior esquerdo)
5. Aguarde o build e instalação das dependências
6. Execute `npm run dev` para iniciar

Pronto! Ambiente configurado em menos de 5 minutos.

Para onboarding de novos desenvolvedores, o tempo de setup cai de horas para minutos. Basta clonar, abrir e aguardar o build automático.

8. Limitações e boas práticas avançadas

Performance em Windows: O compartilhamento de arquivos entre Windows e Linux via bind mount pode ser lento. Soluções:
- Use WSL2 com Docker configurado para WSL
- Armazene o projeto no sistema de arquivos WSL (\\wsl$\)
- Use volumes nomeados para diretórios com muitos arquivos (node_modules, vendor)

Projetos monolíticos vs microsserviços:
- Monolíticos: um único Dev Container com todos os serviços em docker-compose
- Microsserviços: Dev Containers separados por serviço, cada um com seu próprio .devcontainer/

Segurança e recursos:
- Nunca exponha portas do container desnecessariamente
- Use "runArgs": ["--memory=2g", "--cpus=2"] para limitar recursos
- Configure "remoteUser": "node" para evitar usar root dentro do container

Monitoramento: Use o explorador do Docker no VS Code para visualizar logs, recursos e containers em execução.

Referências