Docker Compose para Desenvolvimento: Infraestrutura Local
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_filepara carregar variáveis de ambiente do seu arquivo.env.localem contêineres- Separe
docker-compose.dev.ymldedocker-compose.prod.yml— eles servem a propósitos diferentes- O padrão
depends_on.condition: service_healthyaguarda pela prontidão real, não apenas pelo início do contêiner- Use
profilespara ativar serviços opcionais (e-mail, monitoramento)- Execute
docker compose(v2) e nãodocker-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: [email protected]
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:
| Tipo | Persistência | Desempenho | Usar para |
|---|---|---|---|
| Volume nomeado | Sim | Excelente | Dados da base de dados |
| Montagem de ligação | Sim | Bom (Linux), Ruim (macOS) | Recarregamento a quente do código-fonte |
| tmpfs | Não | Excelente | Arquivos 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ão5432:5432) - Redis: mantenha
6379:6379(raramente entra em conflito) - Execute
lsof -i :5432para 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.
Escrito por
ECOSIRE TeamTechnical Writing
The ECOSIRE technical writing team covers Odoo ERP, Shopify eCommerce, AI agents, Power BI analytics, GoHighLevel automation, and enterprise software best practices. Our guides help businesses make informed technology decisions.
Artigos Relacionados
Guia de implantação do AWS EC2 para aplicativos da Web
Guia completo de implantação do AWS EC2: seleção de instâncias, grupos de segurança, implantação de Node.js, proxy reverso Nginx, SSL, escalonamento automático, monitoramento CloudWatch e otimização de custos.
ERP na nuvem versus ERP local em 2026: o guia definitivo
ERP na nuvem versus ERP local em 2026: análise de custo total, comparação de segurança, escalabilidade, conformidade e o modelo de implantação certo para o seu negócio.
Construindo Módulos Odoo Personalizados: Tutorial para Desenvolvedores
Tutorial passo a passo para construir módulos personalizados do Odoo 19. Abrange estrutura de módulo, modelos, visualizações, segurança, assistentes e práticas recomendadas para código pronto para produção.