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.