Estes padrões valem para qualquer serviço novo dentro de /opt/server/infra/ e idealmente também para projetos em /opt/server/projetos/.

Um compose por serviço

Cada serviço de infra mora numa pasta própria com seu único docker-compose.yml. Não existe compose monolítico que sobe tudo. Por quê: sobe e desce serviço sem reiniciar os outros, conflitos de versão ficam isolados, fica óbvio o que pertence a quem.

Rede server_net external

Todo compose se conecta à rede server_net declarada como external:
networks:
  server_net:
    external: true
A rede é criada uma vez pelo bootstrap.sh. Composes assumem que ela já existe.

Variáveis de ambiente

Cada serviço tem dois arquivos:
  • .env.example — versionável, lista as variáveis com placeholders vazios. Serve como documentação do que precisa ser configurado.
  • .env — real, com segredos preenchidos. Nunca vai pro git.
O bootstrap.sh copia .env.example.env na primeira execução e gera senhas aleatórias para os campos vazios.

Persistência por bind mount

Dados ficam em ./data/ dentro da pasta de cada serviço:
volumes:
  - ./data:/var/lib/postgresql/data
Por quê bind mount em vez de volume nomeado: backup é só copiar a pasta, fica óbvio onde estão os dados, fácil migrar pra outra máquina.

Portas internas only

Apenas o NPM expõe portas no host. Todo o resto fala só dentro da server_net. Para acessar via browser, configure um Proxy Host no NPM. Convenção de bind:
  • Portas públicas (80, 443): bind em 0.0.0.0 (escrito como "80:80").
  • Portas administrativas (ex.: 81 do NPM): bind em 127.0.0.1 (escrito como "127.0.0.1:81:81").
Docker contorna o UFW. Portas publicadas como 81:81 ficam acessíveis externamente mesmo com ufw deny 81/tcp — o Docker grava regras de iptables que rodam antes do UFW. Sempre prefira 127.0.0.1:porta:porta para qualquer coisa administrativa, em vez de confiar no firewall do host.
Por quê: reduz superfície de ataque sem depender de UFW, força HTTPS via NPM, evita conflitos de porta no host.

Nomes de container fixos

Cada compose define container_name explícito (postgres, redis, n8n, npm). Sem isso, o Docker prefixa com o nome da pasta e os serviços não se acham por DNS.

Senhas geradas, nunca digitadas

Senhas vão por openssl rand -hex 24 no bootstrap.sh. Você nunca escolhe senha à mão — elas têm 48 chars hexa e moram apenas no .env correspondente.

Idempotência dos scripts

Tanto bootstrap.sh quanto create-db.sh são seguros pra rodar de novo. Não sobrescrevem segredos existentes nem recriam recursos já presentes.

Versionamento: só infra, sem dados

/opt/server/ é um repositório git privado, mas com .gitignore rigoroso:
  • Vai pro git: composes, scripts, .env.example, documentação.
  • Não vai pro git: .env reais, infra/*/data/, infra/npm/letsencrypt/, infra/postgres/secrets/, e tudo dentro de projetos/ (exceto o .gitkeep).
A regra mental: se foi gerado pelo bootstrap, por um container, ou contém segredo, é ignorado. O repo é receita; dado é responsabilidade do backup. Detalhes em Repositório.