Saltar para o conteúdo
Docker Compose para Desenvolvimento Local: Setup Profissional
DevOps

Docker Compose para Desenvolvimento Local: Setup Profissional

22 de outubro de 2024·Paulo de Paula

“Funciona na minha máquina” é uma frase que Docker acabou com quase todas as desculpas para usar. Mas um docker-compose.yml mal configurado cria novos problemas: volumes lentos, hot reload quebrado, banco que some a cada restart.

O arquivo base

# docker-compose.yml
version: '3.9'

services:
  # ── Aplicação ──────────────────────────────────────────
  api:
    build:
      context: .
      dockerfile: Dockerfile.dev
    ports:
      - "3000:3000"
    volumes:
      - .:/app                          # código-fonte montado
      - /app/node_modules               # node_modules do container (não sobrescrito)
    environment:
      - NODE_ENV=development
      - DATABASE_URL=postgresql://dev:dev@postgres:5432/devdb
      - REDIS_URL=redis://redis:6379
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_started
    restart: unless-stopped

  # ── PostgreSQL ─────────────────────────────────────────
  postgres:
    image: postgres:16-alpine
    environment:
      POSTGRES_USER: dev
      POSTGRES_PASSWORD: dev
      POSTGRES_DB: devdb
    ports:
      - "5432:5432"
    volumes:
      - postgres_data:/var/lib/postgresql/data
      - ./scripts/init.sql:/docker-entrypoint-initdb.d/init.sql
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U dev -d devdb"]
      interval: 5s
      timeout: 5s
      retries: 5
    restart: unless-stopped

  # ── Redis ──────────────────────────────────────────────
  redis:
    image: redis:7-alpine
    command: redis-server --appendonly yes
    ports:
      - "6379:6379"
    volumes:
      - redis_data:/data
    restart: unless-stopped

  # ── Redis UI (desenvolvimento) ─────────────────────────
  redis-commander:
    image: rediscommander/redis-commander:latest
    environment:
      REDIS_HOSTS: "local:redis:6379"
    ports:
      - "8081:8081"
    depends_on: [redis]
    profiles: ["tools"]  # só sobe com: docker compose --profile tools up

volumes:
  postgres_data:
  redis_data:

Dockerfile.dev com hot reload

# Dockerfile.dev
FROM node:20-alpine

WORKDIR /app

# Instala dependências em camada separada (cache)
COPY package*.json ./
RUN npm ci

# Não copia o código — vem do volume em dev
# COPY . .  ← só no Dockerfile de produção

EXPOSE 3000

# nodemon para hot reload
CMD ["npx", "nodemon", "--watch", "src", "--ext", "ts,js", \
     "--exec", "node --loader ts-node/esm src/server.ts"]

O problema do node_modules no volume

Esse é o erro mais comum:

volumes:
  - .:/app  # monta tudo, incluindo seu node_modules local (Windows!)

Em macOS/Windows, seu node_modules local tem binários compilados para o seu OS. Quando montado no container Linux, quebra.

A solução é um volume anônimo que “mascara” o node_modules local:

volumes:
  - .:/app                   # monta o código
  - /app/node_modules        # volume anônimo — isola o node_modules do container

O Docker cria um volume separado para /app/node_modules que sobrepõe o diretório montado. O container usa seus próprios módulos compilados para Linux.

Variáveis de ambiente com .env

# .env.docker (não commitar .env com secrets!)
POSTGRES_PASSWORD=dev
JWT_SECRET=development-secret-only
# docker-compose.yml
services:
  api:
    env_file:
      - .env.docker
    environment:
      - NODE_ENV=development  # sobrescreve .env.docker se necessário
# .gitignore
.env
.env.local
.env.*.local
# NÃO ignorar:
.env.docker.example  # arquivo de exemplo versionado

Comandos do dia a dia

# Sobe tudo em background
docker compose up -d

# Sobe com ferramentas extras (redis-commander, etc.)
docker compose --profile tools up -d

# Logs de um serviço em tempo real
docker compose logs -f api

# Executa comando dentro do container
docker compose exec api npm run db:migrate
docker compose exec api sh  # shell interativo

# Rebuild após mudar Dockerfile ou package.json
docker compose up -d --build api

# Para tudo mas mantém os volumes
docker compose stop

# Para e remove containers (volumes persistem)
docker compose down

# Para, remove containers E volumes (reset total)
docker compose down -v

Override para diferentes contextos

# docker-compose.override.yml (automático em desenvolvimento)
services:
  api:
    environment:
      - DEBUG=*  # verbose logging só no dev

# docker-compose.ci.yml (para CI/CD)
# docker compose -f docker-compose.yml -f docker-compose.ci.yml up
services:
  api:
    environment:
      - NODE_ENV=test

Healthchecks e ordem de inicialização

depends_on:
  postgres:
    condition: service_healthy  # espera o healthcheck passar

Sem condition: service_healthy, o container da API sobe antes do banco estar pronto para aceitar conexões — e sua aplicação falha na inicialização. O healthcheck do postgres com pg_isready resolve isso.

Performance em macOS com volumes

Volumes no macOS são notoriamente lentos. Para projetos grandes:

# Para arquivos que não precisam de sync imediato
volumes:
  - .:/app:cached          # leituras usam cache do host (mais rápido)
  - /app/node_modules      # isolado

No Docker Desktop, ative “VirtioFS” nas configurações para melhor performance em macOS com Apple Silicon.

Commit o docker-compose.yml no repositório. Todo novo dev rodando docker compose up tem o ambiente completo funcionando em minutos — sem “funciona na minha máquina”.