O que Padrões Docker faz?
Práticas recomendadas para Docker e Docker Compose no desenvolvimento conteinerizado.
Docker Compose para Desenvolvimento Local
Stack Padrão de Aplicação Web
# docker-compose.yml
services:
app:
build:
context: .
target: dev # Usa o estágio dev do Dockerfile de múltiplos estágios
ports:
- "3000:3000"
volumes:
- .:/app # Montagem bind para hot reload
- /app/node_modules # Volume anônimo -- preserva dependências do contêiner
environment:
- DATABASE_URL=postgres://postgres:postgres@db:5432/app_dev
- REDIS_URL=redis://redis:6379/0
- NODE_ENV=development
depends_on:
db:
condition: service_healthy
redis:
condition: service_started
command: npm run dev
db:
image: postgres:16-alpine
ports:
- "5432:5432"
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: app_dev
volumes:
- pgdata:/var/lib/postgresql/data
- ./scripts/init-db.sql:/docker-entrypoint-initdb.d/init.sql
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 3s
retries: 5
redis:
image: redis:7-alpine
ports:
- "6379:6379"
volumes:
- redisdata:/data
mailpit: # Teste de email local
image: axllent/mailpit
ports:
- "8025:8025" # Interface web
- "1025:1025" # SMTP
volumes:
pgdata:
redisdata:
Dockerfile de Desenvolvimento vs Produção
# Estágio: dependências
FROM node:22-alpine AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
# Estágio: dev (hot reload, ferramentas de depuração)
FROM node:22-alpine AS dev
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
EXPOSE 3000
CMD ["npm", "run", "dev"]
# Estágio: build
FROM node:22-alpine AS build
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npm run build && npm prune --production
# Estágio: produção (imagem mínima)
FROM node:22-alpine AS production
WORKDIR /app
RUN addgroup -g 1001 -S appgroup && adduser -S appuser -u 1001
USER appuser
COPY --from=build --chown=appuser:appgroup /app/dist ./dist
COPY --from=build --chown=appuser:appgroup /app/node_modules ./node_modules
COPY --from=build --chown=appuser:appgroup /app/package.json ./
ENV NODE_ENV=production
EXPOSE 3000
HEALTHCHECK --interval=30s --timeout=3s CMD wget -qO- http://localhost:3000/health || exit 1
CMD ["node", "dist/server.js"]
Arquivos de Substituição
# docker-compose.override.yml (carregado automaticamente, configurações apenas para dev)
services:
app:
environment:
- DEBUG=app:*
- LOG_LEVEL=debug
ports:
- "9229:9229" # Depurador Node.js
# docker-compose.prod.yml (explícito para produção)
services:
app:
build:
target: production
restart: always
deploy:
resources:
limits:
cpus: "1.0"
memory: 512M
# Desenvolvimento (carrega override automaticamente)
docker compose up
# Produção
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d
Redes
Descoberta de Serviços
Serviços na mesma rede Compose são resolvidos pelo nome do serviço:
# Do contêiner "app":
postgres://postgres:postgres@db:5432/app_dev # "db" resolve para o contêiner db
redis://redis:6379/0 # "redis" resolve para o contêiner redis
Redes Personalizadas
services:
frontend:
networks:
- frontend-net
api:
networks:
- frontend-net
- backend-net
db:
networks:
- backend-net # Acessível apenas pela api, não pelo frontend
networks:
frontend-net:
backend-net:
Expondo Apenas o Necessário
services:
db:
ports:
- "127.0.0.1:5432:5432" # Acessível apenas pelo host, não pela rede
# Omita portas em produção -- acessível apenas dentro da rede Docker
Estratégias de Volumes
volumes:
# Volume nomeado: persiste entre reinicializações do contêiner, gerenciado pelo Docker
pgdata:
# Montagem bind: mapeia diretório do host para o contêiner (para desenvolvimento)
# - ./src:/app/src
# Volume anônimo: preserva conteúdo gerado pelo contêiner da substituição da montagem bind
# - /app/node_modules
Padrões Comuns
services:
app:
volumes:
- .:/app # Código fonte (montagem bind para hot reload)
- /app/node_modules # Protege node_modules do contêiner do host
- /app/.next # Protege cache de build
db:
volumes:
- pgdata:/var/lib/postgresql/data # Dados persistentes
- ./scripts/init.sql:/docker-entrypoint-initdb.d/init.sql # Scripts de inicialização
Segurança de Contêineres
Hardening do Dockerfile
# 1. Use tags específicas (nunca :latest)
FROM node:22.12-alpine3.20
# 2. Execute como não-root
RUN addgroup -g 1001 -S app && adduser -S app -u 1001
USER app
# 3. Remova capacidades (no compose)
# 4. Sistema de arquivos raiz somente leitura quando possível
# 5. Sem segredos nas camadas da imagem
Segurança no Compose
services:
app:
security_opt:
- no-new-privileges:true
read_only: true
tmpfs:
- /tmp
- /app/.cache
cap_drop:
- ALL
cap_add:
- NET_BIND_SERVICE # Apenas se precisar vincular a portas < 1024
Gerenciamento de Segredos
# BOM: Use variáveis de ambiente (injetadas em tempo de execução)
services:
app:
env_file:
- .env # Nunca commite .env no git
environment:
- API_KEY # Herda do ambiente do host
# BOM: Segredos Docker (modo Swarm)
secrets:
db_password:
file: ./secrets/db_password.txt
services:
db:
secrets:
- db_password
# RUIM: Hardcoded na imagem
# ENV API_KEY=sk-proj-xxxxx # NUNCA FAÇA ISSO
Harnesses de Instalação CLI Reforçados
Use contêineres para testar o comportamento do instalador em cópias descartáveis do projeto sem permitir que o teste modifique o checkout da fonte.
Respeite o Limite da Plataforma
- Execute contêineres reais para distribuições Linux como Debian e Ubuntu.
- macOS não pode ser executado como um contêiner Docker porque o Docker compartilha um kernel Linux. Execute o mesmo ponto de entrada de teste sem shell nativamente no macOS.
- Contêineres Windows exigem um engine Docker Windows. Execute lógica independente de plataforma em um runner CI Windows nativo e reserve contêineres Windows para um host Windows.
- Mantenha uma matriz CI nativa Ubuntu/macOS/Windows para caminhos específicos do host, shims de comando, citações e comportamento do sistema de arquivos.
Não afirme que um contêiner Linux valida o comportamento do macOS ou Windows.
Imponha o Contrato de Isolamento
- Fixe as imagens base por digest imutável e fixe as versões CLI instaladas.
- Execute como um UID/GID numérico não-root quando os nomes de conta da distribuição diferirem.
- Monte o repositório e o projeto fonte como somente leitura.
- Copie o projeto fonte para um workspace
tmpfsgravável antes de qualquer mutação. - Monte
/workspacecomnoexec, UID/GID 1000 emode=0700para que apenas o usuário do contêiner possa inspecionar os dados do projeto. - Mantenha o cache executável do npm e npx em
NPM_CONFIG_CACHE=/tmp/npm-cacheno ponto de montagem executável/tmp. Seu tamanho padrão é 2 GiB e pode ser ajustado comECC_TMPFS_SIZE;ECC_WORKSPACE_SIZEcontrola separadamente a montagem do workspace privado. - Defina
read_only: true,no-new-privileges:true,cap_drop: [ALL]e umpids_limitfinito. - Mantenha os serviços CLI reais padrão em
network_mode: none. Adicione acesso à rede apenas por meio de um serviço opt-in visivelmente nomeado para uma sessão de provedor autenticada; nunca torne isso um padrão acidental orientado por ambiente. - Crie apenas os caminhos temporários graváveis que a ferramenta precisa.
- Não passe credenciais do host para o contêiner por padrão.
- Padrão para uma execução a seco e lista de permissões apenas os modos explícitos
dry-run,install,plugineshell. - Use arrays de argumentos ou
spawnSync(..., { shell: false })para runners multiplataforma. Nunca interpole caminhos de projeto em um comando shell.
Exercite o Harness de Configuração do Plugin ECC
Use docker/plugin-setup/compose.yaml como a implementação de referência. Ele fornece:
fixture-testspara o manifesto de instalação focado, alvo e suíte de executor.real-clipara a imagem Linux genérica baseada em Debian fixada.real-cli-ubuntupara a imagem Ubuntu fixada.
Valide o modelo Compose antes de construir:
docker compose -f docker/plugin-setup/compose.yaml config --quiet
Construa ambas as imagens Linux reais:
docker compose -f docker/plugin-setup/compose.yaml \
build real-cli real-cli-ubuntu
Execute o fluxo padrão seguro em cada imagem:
docker compose -p ecc-plugin-debian-test \
-f docker/plugin-setup/compose.yaml \
run --rm -T real-cli dry-run
docker compose -p ecc-plugin-ubuntu-test \
-f docker/plugin-setup/compose.yaml \
run --rm -T real-cli-ubuntu dry-run
A execução a seco executa o contrato de comando público atual:
ecc install --profile core --target claude-project --dry-run --json
Antes desse comando ser executado, o contêiner cria um artefato npm empacotado localmente
a partir do checkout somente leitura com npm pack --ignore-scripts. Ele extrai o
tarball criado por si mesmo em /tmp, valida o nome do pacote ecc-universal,
manifestos de instalação necessários e o mapeamento bin.ecc confinado do
package.json, então invoca o executável ecc extraído. O tempo de execução permanece em
network_mode: none, não executa scripts de ciclo de vida do pacote e não
depende de node_modules do host; suas dependências de produção exatas fixadas já estão
presentes na imagem.
O harness rejeita um plano vazio, um alvo diferente de claude-project, qualquer operação
fora de /workspace/project/.claude, ou qualquer execução a seco que crie o diretório
alvo. install realiza a aplicação isolada duas vezes, verifica seu estado de
instalação gerenciado, lista o alvo instalado e executa doctor.
Iniciar, Abrir, Reconectar e Limpar uma Sessão Nomeada
Inicie um contêiner destacado sem --rm para que sair do terminal não remova
a sessão:
docker compose -p ecc-plugin-session \
-f docker/plugin-setup/compose.yaml \
run --detach --name ecc-plugin-shell real-cli shell
O contêiner copia a fixture somente leitura para o diretório privado estável
/workspace/project. Confirme que está em execução, então emita o lado Docker do
contrato de dados do abridor de terminal v1:
docker inspect --format '{{.State.Running}}' ecc-plugin-shell
node docker/plugin-setup/interactive-plan.js \
--container ecc-plugin-shell \
--workdir /workspace/project \
--json \
-- bash
O resultado JSON tem exatamente um limite executable e argv (mais
contractVersion: 1): o executável é docker, e argv começa com
exec, -it, e -w. Passe esses dados para a skill separada de abridor de terminal
quando ela estiver instalada. Este harness Docker deliberadamente não importa um
adaptador de terminal, interpola um comando shell ou gerencia um processo GUI do host.
Até lá, abra o mesmo PTY no terminal host atual diretamente:
docker exec -it -w /workspace/project ecc-plugin-shell bash
Saia do shell sem parar o contêiner destacado. Reconecte com o
mesmo comando docker exec -it. Quando terminar, remova o contêiner nomeado exato
e seus recursos do projeto Compose:
docker rm --force ecc-plugin-shell
docker compose -p ecc-plugin-session \
-f docker/plugin-setup/compose.yaml \
down --remove-orphans
Credenciais do host estão ausentes por padrão e diretórios de credenciais nunca são
montados. O serviço padrão também não tem acesso à rede. Quando uma sessão de provedor
autenticada genuinamente precisa de uma rede, construa real-cli primeiro e então opte
visivelmente com docker compose --profile networked run real-cli-networked shell. Prefira autenticar dentro dessa sessão descartável. Se uma execução CI deve
herdar uma credencial de ambiente do host, torne isso opt-in na invocação com um
sinalizador --env NAME explícito do Compose, entenda que o valor é inspecionável
e pode ser exfiltrado durante a vida útil do contêiner, e remova o contêiner nomeado
exato imediatamente após.
Execute a mesma suíte focada nativamente no host:
npm run test:plugin-setup-platform
Inspecione a identidade e o ambiente produzidos antes de confiar na imagem:
docker image inspect ecc-plugin-setup:debian ecc-plugin-setup:ubuntu
Limpe cada projeto de teste nomeado sem excluir volumes ou imagens não relacionados:
docker compose -p ecc-plugin-debian-test \
-f docker/plugin-setup/compose.yaml down --remove-orphans
docker compose -p ecc-plugin-ubuntu-test \
-f docker/plugin-setup/compose.yaml down --remove-orphans
.dockerignore
node_modules
.git
.env
.env.*
dist
coverage
*.log
.next
.cache
docker-compose*.yml
Dockerfile*
README.md
tests/
Depuração
Comandos Comuns
# Visualizar logs
docker compose logs -f app # Seguir logs do app
docker compose logs --tail=50 db # Últimas 50 linhas do db
# Executar comandos no contêiner em execução
docker compose exec app sh # Shell no app
docker compose exec db psql -U postgres # Conectar ao postgres
# Inspecionar
docker compose ps # Serviços em execução
docker compose top # Processos em cada contêiner
docker stats # Uso de recursos
# Reconstruir
docker compose up --build # Reconstruir imagens
docker compose build --no-cache app # Forçar reconstrução completa
# Limpar
docker compose down # Parar e remover contêineres
docker compose down -v # Também remover volumes (DESTRUTIVO)
docker system prune # Remover imagens/contêineres não utilizados
Depuração de Problemas de Rede
# Verificar resolução DNS dentro do contêiner
docker compose exec app nslookup db
# Verificar conectividade
docker compose exec app wget -qO- http://api:3000/health
# Inspecionar rede
docker network ls
docker network inspect <project>_default
Antipadrões
# RUIM: Usar :latest
FROM node:latest
# RUIM: Executar como root
USER root
# RUIM: Hardcodear segredos
ENV DATABASE_URL=postgres://user:pass@host:5432/db
# RUIM: Expor portas desnecessárias
ports:
- "5432:5432"