Pular para o conteúdo principal

Docker Compose para Desenvolvimento: Infraestrutura Local

Docker Compose para desenvolvimento local: PostgreSQL, Redis, Authentik, rede, verificações de integridade, gerenciamento de volume e configurações específicas do ambiente para monorepos TypeScript.

E
ECOSIRE Research and Development Team
|19 de março de 202611 min de leitura2.0k Palavras|

A diferença entre uma experiência de integração agradável ("clonar e executar pnpm dev:infra") e uma experiência dolorosa ("primeiro configure o PostgreSQL, depois configure o Redis e depois...") se resume a quão bem a configuração do Docker Compose captura seus requisitos de infraestrutura.

A diferença entre uma experiência de integração agradável ("clonar e executar pnpm dev:infra") e uma experiência dolorosa ("primeiro configure o PostgreSQL, depois configure o Redis e depois...") se resume a quão bem a configuração do Docker Compose captura seus requisitos de infraestrutura. Um docker-compose.dev.yml bem elaborado permite que qualquer desenvolvedor em qualquer máquina tenha exatamente a mesma infraestrutura em execução em minutos.

Este guia aborda os padrões para uma pilha de desenvolvimento local com qualidade de produção: configuração de serviço, verificações de integridade, rede, gerenciamento de volume e integração com a sequência de inicialização do seu aplicativo.

Principais conclusões

  • Use uma porta não padrão para PostgreSQL localmente (5433) para evitar conflitos com instalações do sistema
  • Verificações de integridade nas dependências de serviço evitam erros de inicialização de "conexão recusada"
  • Os volumes nomeados persistem os dados do banco de dados entre as reinicializações do contêiner – as montagens de ligação não funcionam de maneira confiável no Windows
  • Use env_file para carregar variáveis de ambiente do seu arquivo .env.local em contêineres
  • Separe docker-compose.dev.yml de docker-compose.prod.yml — eles servem a propósitos diferentes
  • O padrão depends_on.condition: service_healthy aguarda pela prontidão real, não apenas pelo início do contêiner
  • Use profiles para ativar serviços opcionais (e-mail, monitoramento)
  • Execute docker compose (v2) e não docker-compose (v1) — a sintaxe do plugin é atual

A pilha completa de desenvolvimento

# infrastructure/docker-compose.dev.yml
name: ecosire-dev

services:
  # ─── PostgreSQL ─────────────────────────────────────────────────
  postgres:
    image: postgres:17-alpine
    container_name: ecosire-postgres
    environment:
      POSTGRES_DB: ecosire_dev
      POSTGRES_USER: ecosire
      POSTGRES_PASSWORD: dev_password_change_in_prod
    ports:
      - "5433:5432"  # 5433 externally — avoids conflicts with system PostgreSQL
    volumes:
      - postgres_data:/var/lib/postgresql/data
      - ./init-scripts:/docker-entrypoint-initdb.d  # Run SQL on first start
    command: >
      postgres
        -c shared_buffers=256MB
        -c effective_cache_size=1GB
        -c work_mem=16MB
        -c maintenance_work_mem=128MB
        -c checkpoint_completion_target=0.9
        -c wal_buffers=16MB
        -c max_connections=100
        -c log_min_duration_statement=100
        -c log_statement=ddl
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ecosire -d ecosire_dev"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 10s
    restart: unless-stopped

  # ─── Redis ──────────────────────────────────────────────────────
  redis:
    image: redis:7-alpine
    container_name: ecosire-redis
    ports:
      - "6379:6379"
    volumes:
      - redis_data:/data
    command: >
      redis-server
        --maxmemory 512mb
        --maxmemory-policy allkeys-lru
        --appendonly yes
        --appendfsync everysec
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 5s
      retries: 5
    restart: unless-stopped

  # ─── Authentik (Identity Provider) ──────────────────────────────
  authentik-server:
    image: ghcr.io/goauthentik/server:2024.12
    container_name: ecosire-authentik
    command: server
    environment:
      AUTHENTIK_REDIS__HOST: redis
      AUTHENTIK_POSTGRESQL__HOST: postgres
      AUTHENTIK_POSTGRESQL__USER: ecosire
      AUTHENTIK_POSTGRESQL__PASSWORD: dev_password_change_in_prod
      AUTHENTIK_POSTGRESQL__NAME: authentik
      AUTHENTIK_SECRET_KEY: dev-secret-key-change-in-production-32chars
      AUTHENTIK_ERROR_REPORTING__ENABLED: "false"
      AUTHENTIK_DISABLE_STARTUP_ANALYTICS: "true"
    volumes:
      - authentik_media:/media
      - authentik_certs:/certs
    ports:
      - "9000:9000"  # HTTP
      - "9443:9443"  # HTTPS
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_healthy
    healthcheck:
      test: ["CMD-SHELL", "ak healthcheck"]
      interval: 30s
      timeout: 10s
      retries: 5
      start_period: 60s  # Authentik takes time to initialize
    restart: unless-stopped

  authentik-worker:
    image: ghcr.io/goauthentik/server:2024.12
    container_name: ecosire-authentik-worker
    command: worker
    environment:
      AUTHENTIK_REDIS__HOST: redis
      AUTHENTIK_POSTGRESQL__HOST: postgres
      AUTHENTIK_POSTGRESQL__USER: ecosire
      AUTHENTIK_POSTGRESQL__PASSWORD: dev_password_change_in_prod
      AUTHENTIK_POSTGRESQL__NAME: authentik
      AUTHENTIK_SECRET_KEY: dev-secret-key-change-in-production-32chars
    volumes:
      - authentik_media:/media
      - authentik_certs:/certs
      - /var/run/docker.sock:/var/run/docker.sock  # For Authentik's proxy
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_healthy
    restart: unless-stopped

  # ─── Mailpit (Email Testing) ─────────────────────────────────────
  mailpit:
    image: axllent/mailpit:latest
    container_name: ecosire-mailpit
    ports:
      - "1025:1025"  # SMTP
      - "8025:8025"  # Web UI
    environment:
      MP_MAX_MESSAGES: 200
      MP_SMTP_AUTH_ACCEPT_ANY: true
      MP_SMTP_AUTH_ALLOW_INSECURE: true
    restart: unless-stopped
    profiles:
      - email  # Optional — use `docker compose --profile email up`

  # ─── pgAdmin (Database GUI) ─────────────────────────────────────
  pgadmin:
    image: dpage/pgadmin4:latest
    container_name: ecosire-pgadmin
    environment:
      PGADMIN_DEFAULT_EMAIL: admin@local.dev
      PGADMIN_DEFAULT_PASSWORD: admin
      PGADMIN_CONFIG_SERVER_MODE: "False"
      PGADMIN_CONFIG_MASTER_PASSWORD_REQUIRED: "False"
    ports:
      - "5050:80"
    volumes:
      - pgadmin_data:/var/lib/pgadmin
    depends_on:
      postgres:
        condition: service_healthy
    restart: unless-stopped
    profiles:
      - tools  # Optional

networks:
  default:
    name: ecosire-dev-network

volumes:
  postgres_data:
    name: ecosire-postgres-data
  redis_data:
    name: ecosire-redis-data
  authentik_media:
    name: ecosire-authentik-media
  authentik_certs:
    name: ecosire-authentik-certs
  pgadmin_data:
    name: ecosire-pgadmin-data

Scripts Package.json

Conecte os comandos do Docker Compose aos seus scripts monorepo:

{
  "scripts": {
    "dev:infra": "docker compose -f infrastructure/docker-compose.dev.yml up -d",
    "dev:infra:down": "docker compose -f infrastructure/docker-compose.dev.yml down",
    "dev:infra:logs": "docker compose -f infrastructure/docker-compose.dev.yml logs -f",
    "dev:infra:reset": "docker compose -f infrastructure/docker-compose.dev.yml down -v && pnpm dev:infra",
    "dev:infra:email": "docker compose -f infrastructure/docker-compose.dev.yml --profile email up -d",
    "dev:infra:tools": "docker compose -f infrastructure/docker-compose.dev.yml --profile tools up -d"
  }
}

O sinalizador --profile permite que serviços opcionais (teste de e-mail com Mailpit, GUI de banco de dados com pgAdmin) permaneçam inativos até serem solicitados explicitamente.


Scripts de inicialização de banco de dados

Coloque os arquivos SQL em infrastructure/init-scripts/ — eles são executados na primeira inicialização do contêiner:

-- infrastructure/init-scripts/01-create-databases.sql
-- Create all databases Authentik needs separately from the app DB
CREATE DATABASE authentik;
GRANT ALL PRIVILEGES ON DATABASE authentik TO ecosire;

-- Create test database for CI
CREATE DATABASE ecosire_test;
GRANT ALL PRIVILEGES ON DATABASE ecosire_test TO ecosire;
-- infrastructure/init-scripts/02-extensions.sql
-- Enable PostgreSQL extensions
\c ecosire_dev;
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
CREATE EXTENSION IF NOT EXISTS "pg_trgm";  -- Trigram search
CREATE EXTENSION IF NOT EXISTS "btree_gin"; -- Composite GIN indexes

\c ecosire_test;
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";

Os scripts de inicialização são executados em ordem alfabética. O metacomando \c database_name psql alterna o banco de dados ativo.


Integração de variáveis de ambiente

Seu aplicativo lê .env.local na raiz do monorepo. Os serviços Docker precisam saber como se conectar entre si usando nomes de serviço (não localhost):

# .env.local (monorepo root)

# PostgreSQL — use 5433 externally (host) or 5432 internally (container network)
DATABASE_URL=postgresql://ecosire:dev_password_change_in_prod@localhost:5433/ecosire_dev

# Redis
REDIS_URL=redis://localhost:6379

# Authentik — use 9000 for external calls from your dev machine
AUTHENTIK_URL=http://localhost:9000
# Use service name for server-to-server calls within Docker network
AUTHENTIK_INTERNAL_URL=http://authentik-server:9000

# Email (Mailpit SMTP)
SMTP_HOST=localhost
SMTP_PORT=1025
SMTP_SECURE=false

# Application
NODE_ENV=development

Para aplicativos executados dentro do Docker que precisam se comunicar com outros serviços, use nomes de serviço. Para aplicativos em execução em sua máquina host (NestJS, Next.js no modo dev), use localhost com as portas mapeadas pelo host.


Aprofundamento das verificações de saúde

As verificações de integridade evitam falhas de inicialização em cascata. O depends_on.condition: service_healthy aguarda a prontidão real, não apenas o início do contêiner:

# Without health checks — can fail because PostgreSQL isn't ready
depends_on:
  - postgres

# With health checks — waits for PostgreSQL to accept connections
depends_on:
  postgres:
    condition: service_healthy

Verificações de integridade personalizadas para seus próprios serviços:

// apps/api/src/health/health.controller.ts
@Get()
@Public()
@HealthCheck()
async check() {
  return this.health.check([
    () => this.db.isHealthy('database'),
    () => this.redis.isHealthy('redis'),
  ]);
}
# If your API is also dockerized
api:
  image: ecosire-api:latest
  healthcheck:
    test: ["CMD", "curl", "-f", "http://localhost:3001/api/health"]
    interval: 30s
    timeout: 10s
    retries: 3
    start_period: 30s

Gerenciamento de volume

Os volumes nomeados persistem dados entre reinicializações. Entenda quando usar cada tipo de volume:

TipoPersistênciaDesempenhoUsar para
Volume nomeadoSimExcelenteDados da base de dados
Montagem de ligaçãoSimBom (Linux), Ruim (macOS)Recarregamento a quente do código-fonte
tmpfsNãoExcelenteArquivos temporários, segredos
# Use bind mounts for source code (enables hot reload)
volumes:
  - ./apps/api/src:/app/src  # Code changes reflected immediately

# Use named volumes for data
volumes:
  - postgres_data:/var/lib/postgresql/data

# Use tmpfs for ephemeral data
volumes:
  - type: tmpfs
    target: /tmp

No macOS com Docker Desktop, as montagens vinculadas usam gRPC FUSE, que é significativamente mais lento do que no Linux. Para os servidores de desenvolvimento NestJS e Next.js, execute-os diretamente em sua máquina host (não no Docker) para obter desempenho nativo do sistema de arquivos.


Produção Docker Compose

O arquivo de composição de produção é estruturalmente diferente — sem portas locais, políticas de reinicialização, limites de recursos de produção:

# infrastructure/docker-compose.prod.yml
name: ecosire-prod

services:
  postgres:
    image: postgres:17-alpine
    environment:
      POSTGRES_DB: ${POSTGRES_DB}
      POSTGRES_USER: ${POSTGRES_USER}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
    volumes:
      - postgres_data:/var/lib/postgresql/data
    # No port mapping — only accessible within Docker network
    restart: always
    deploy:
      resources:
        limits:
          memory: 2G
        reservations:
          memory: 512M

  redis:
    image: redis:7-alpine
    command: redis-server --requirepass ${REDIS_PASSWORD} --maxmemory 1gb
    volumes:
      - redis_data:/data
    restart: always
    # No port mapping — internal only

volumes:
  postgres_data:
  redis_data:

A produção não expõe as portas externamente — os aplicativos se conectam por meio da rede interna do Docker. Nginx lida com tráfego externo.


Armadilhas e soluções comuns

Armadilha 1: Conflitos de porta com serviços do sistema

PostgreSQL, Redis e outros serviços geralmente são executados como serviços de sistema. Sempre mapeie para portas não padrão em desenvolvimento:

  • PostgreSQL: 5433:5432 (não 5432:5432)
  • Redis: mantenha 6379:6379 (raramente entra em conflito)
  • Execute lsof -i :5432 para verificar o que está usando a porta padrão

Armadilha 2: Problemas de permissão de volume no Linux

Os volumes Docker no Linux usam propriedade root por padrão. Se o usuário do seu contêiner não for root, defina a propriedade correta:

postgres:
  image: postgres:17-alpine
  user: "999:999"  # postgres user's UID:GID
  # Or use init container to fix permissions

Armadilha 3: a inicialização do Authentik leva mais de 60 segundos

Authentik executa migrações de banco de dados na primeira inicialização. O start_period: 60s na verificação de integridade dá tempo. Se os serviços dependentes começarem antes do Authentik estar pronto, eles falharão. Use a condição service_healthy e forneça start_period suficiente.

Armadilha 4: Limites de recursos do Docker Desktop no Mac

O Docker Desktop padrão aloca 2 CPUs e 2 GB de RAM – não o suficiente para que PostgreSQL + Redis + Authentik sejam executados simultaneamente. Aumente as configurações do Docker Desktop > Recursos para pelo menos 4 CPUs e 6 GB de RAM.

Armadilha 5: docker-compose vs docker compose

O antigo docker-compose (v1, escrito em Python) está obsoleto. Use docker compose (v2, o plugin). Verifique sua versão: docker compose version. Se você vir Docker Compose version v2.x.x, você está usando a v2.


Perguntas frequentes

Devo executar meus serviços de aplicação (NestJS, Next.js) no Docker durante o desenvolvimento?

Geralmente não — para desenvolvimento ativo, execute seus serviços de aplicativo em sua máquina host para recarregar a quente mais rapidamente e depurar mais facilmente. Use o Docker apenas para serviços de infraestrutura (bancos de dados, caches, provedores de identidade) que sejam estáveis ​​e não precisem de reinicialização frequente. A exceção é se o seu aplicativo tiver dependências nativas que diferem entre o sistema operacional de desenvolvimento e o ambiente de produção.

Como lidar com migrações de banco de dados no fluxo de trabalho do Docker Compose?

Execute migrações da sua máquina host após iniciar a infraestrutura: pnpm dev:infra && pnpm db:migrate. Não execute migrações dentro de um contêiner Docker durante o desenvolvimento — você perde a verificação de tipo e a integração IDE que tornam as migrações do Drizzle seguras. Para a criação inicial do banco de dados, use os scripts initdb.d do Docker.

Como faço backup e restauro meus volumes locais do Docker?

Use docker run --rm -v postgres_data:/data -v $(pwd):/backup alpine tar czf /backup/postgres-backup.tar.gz /data para fazer backup. Restaure com a mesma abordagem usando tar xzf. Para desenvolvimento, você também pode fazer dump com pg_dump e restaurar com psql já que a porta está exposta.

Como compartilho o estado do Docker Compose com outros membros da equipe?

O arquivo Docker Compose é compartilhado via git, mas os dados nos volumes são locais. Cada desenvolvedor começa com um banco de dados vazio e executa migrações/sementes para preenchê-lo. Use scripts iniciais (comprometidos com o repositório) para criar dados de teste consistentes. O docker-compose.dev.yml compartilhado garante que todos usem as mesmas versões e configurações de serviço.

Por que usar Mailpit em vez de e-mail real no desenvolvimento?

Mailpit é um servidor SMTP local que captura todos os e-mails enviados e fornece uma interface da web para visualizá-los. Ele evita o envio acidental de e-mails reais para usuários reais durante o desenvolvimento, não requer credenciais SMTP e permite verificar modelos de e-mail sem verificar sua caixa de entrada. Configure seu aplicativo para usar SMTP_HOST=localhost SMTP_PORT=1025 e visite http://localhost:8025 para ver os e-mails capturados.


Próximas etapas

Uma configuração Docker Compose bem elaborada para desenvolvimento local é um investimento que paga dividendos sempre que um novo desenvolvedor entra ou você inicia uma nova máquina. ECOSIRE executa PostgreSQL 17, Redis 7 e Authentik no Docker Compose para desenvolvimento local em toda a equipe.

Precisa de ajuda para projetar sua infraestrutura de desenvolvimento local ou colocar seu aplicativo em contêineres para produção? Explore nossos serviços DevOps para ver como podemos ajudar.

E

Escrito por

ECOSIRE Research and Development Team

Redação técnica

A equipe de redação técnica da ECOSIRE escreve sobre Odoo ERP, e-commerce com Shopify, agentes de IA, análise de dados com Power BI, automação com GoHighLevel e boas práticas de software empresarial. Nossos guias ajudam as empresas a tomar decisões de tecnologia bem fundamentadas.

ECOSIRE

Expanda o seu negócio com ECOSIRE

Soluções empresariais em ERP, comércio eletrônico, IA, análise e automação.