Sempre que faltar um serviço compartilhado novo (ex.: MinIO para S3, Mailhog para testes de email, Grafana para observabilidade), siga este padrão.

Passos

1

Criar a pasta do serviço

mkdir /opt/server/infra/<servico>
cd /opt/server/infra/<servico>
2

Escrever docker-compose.yml

Template mínimo:
services:
  <servico>:
    image: <imagem>:<tag-lts>
    container_name: <servico>
    restart: unless-stopped
    env_file: .env
    volumes:
      - ./data:/caminho/dos/dados/no/container
    networks:
      - server_net

networks:
  server_net:
    external: true
Não exponha portas no host, a menos que seja absolutamente necessário (NPM é a exceção).
3

Criar .env.example

Liste as variáveis com placeholders vazios. Ex.:
ADMIN_USER=admin
ADMIN_PASSWORD=
Se for um campo que o bootstrap.sh deve preencher automaticamente com senha gerada, adicione o bloco correspondente em bootstrap.sh:
echo "==> <servico>"
ensure_env_from_example <servico>
if grep -qE '^ADMIN_PASSWORD=$|^ADMIN_PASSWORD=changeme' <servico>/.env 2>/dev/null; then
  set_env_var <servico>/.env ADMIN_PASSWORD "$(gen_secret)"
  echo "  generated ADMIN_PASSWORD"
fi
4

Rodar bootstrap

cd /opt/server/infra
./bootstrap.sh
Idempotente — não afeta os serviços já rodando.
5

Subir o serviço

cd /opt/server/infra/<servico>
docker compose up -d
6

Expor via NPM (se tiver UI web)

Na UI do NPM (:81), criar Proxy Host:
  • Domain: <servico>.example.com
  • Forward: <servico>:<porta-interna>
  • SSL: Let’s Encrypt + Force SSL
Detalhes em NPM.
7

Documentar

Criar docs/infra/<servico>.mdx seguindo o mesmo formato dos existentes (frontmatter, imagem, variáveis, volumes, como subir, padrão de uso). Adicionar o arquivo em docs/docs.json na navegação.

Checklist

  • Pasta infra/<servico>/ criada
  • docker-compose.yml usa server_net external, nome do container fixo, restart unless-stopped, sem portas expostas (salvo exceção)
  • .env.example versionado com todos os campos necessários
  • bootstrap.sh atualizado se houver senha a gerar
  • Bind mount em ./data/ para persistência
  • Se o container roda como user não-root e não faz self-chown do volume (caso do n8n com UID 1000), adicionar bloco no bootstrap.sh que faz docker run --rm -v $(pwd)/<servico>/data:/data alpine chown -R <UID>:<GID> /data
  • Proxy Host configurado no NPM (se aplicável)
  • Página docs/infra/<servico>.mdx criada e adicionada em docs.json