- Castro/
- blog/
- Semaphore self-hosted com PostgreSQL, Traefik e secrets por arquivo: como montei o control plane/
Semaphore self-hosted com PostgreSQL, Traefik e secrets por arquivo: como montei o control plane
Arquitetura de um control plane Semaphore em Docker com banco dedicado, redes separadas, secrets por arquivo, CLIs de cloud e executor não-root.
Quando o Semaphore deixa de ser uma interface para testes e passa a executar automações de produção, eu começo a tratá-lo como infraestrutura de controle.
Isso muda a preocupação.
Não basta o container subir.
Quero saber:
| |
A stack privada que usei como referência responde essas perguntas de forma relativamente simples com Docker Compose.
Dois serviços principais #
O núcleo é:
| |
O banco usa PostgreSQL 16 Alpine e fica apenas na rede interna da stack.
O Semaphore depende do health check do banco antes de iniciar.
Conceitualmente:
| |
Isso não resolve migração de schema ou restore, mas evita iniciar o control plane antes de o banco estar pronto para conexões.
A porta local de debug não precisa ficar aberta na internet #
Na stack real, a UI tem um bind local semelhante a:
| |
Ou seja, a porta auxiliar serve para diagnóstico no host, não como publicação externa.
O acesso externo passa pelo reverse proxy.
Essa diferença é importante:
| |
Rede interna e rede do proxy #
O banco participa somente da rede interna.
O Semaphore participa de:
| |
A topologia fica:
| |
Banco não precisa de porta publicada no host para atender a aplicação.
Secrets entram por arquivo #
Em vez de colocar senhas diretamente no Compose, a stack usa arquivos montados como Docker secrets/secret files.
Exemplos conceituais:
| |
O Compose aponta variáveis como:
| |
Isso separa o template de infraestrutura do valor secreto.
.gitignore também precisa conhecer o que é operacional #
Na fonte privada, ficam fora do Git categorias como:
| |
Além de extensões comuns de chaves, dumps e arquivos de ambiente.
Isso é uma boa barreira contra commit acidental.
Mas existe uma ressalva importante:
.gitignore não apaga segredo que já entrou no histórico.
Por isso uma distribuição pública deve nascer em repositório novo e passar por secret scan, em vez de assumir que ignorar arquivos basta.
AWS e OCI entram read-only no filesystem #
Configurações cloud são montadas no executor como read-only.
Conceitualmente:
| |
Isso impede que uma task comum altere o arquivo do host por acidente através daquele mount.
Ainda é material sensível e precisa de permissões corretas fora do container.
O executor precisa das ferramentas que os playbooks realmente usam #
A imagem privada parte de uma versão fixa do Semaphore e adiciona ferramentas operacionais.
Entre elas:
| |
Também instala bibliotecas Python para bancos e Kubernetes.
Isso transforma o container num executor multi-cloud/multi-stack, em vez de depender do host ter cada CLI instalada.
Instalar como root e executar como usuário não-root #
O Dockerfile precisa de root durante a instalação de pacotes.
Depois, a imagem cria/ajusta diretórios e volta para o usuário do Semaphore.
A sequência é conceitualmente:
| |
É melhor do que deixar o executor permanentemente como root só porque o build precisou de privilégios.
Ferramenta demais também aumenta superfície #
Há um trade-off.
Colocar AWS, OCI, Kubernetes, bancos e SSH na mesma imagem facilita automação heterogênea.
Também aumenta:
- quantidade de pacotes;
- superfície de vulnerabilidade;
- tempo de build;
- necessidade de atualização;
- poder disponível para um playbook comprometido.
Em um blueprint público, eu documentaria dois perfis:
| |
ou pelo menos deixaria claro que ferramentas extras devem ser removidas quando não forem necessárias.
Paralelismo é guardrail do control plane #
A configuração privada define limite máximo de tarefas paralelas e duração máxima de task.
Esses parâmetros são úteis porque um orquestrador pode amplificar erro.
Se um operador dispara várias automações grandes simultaneamente, o problema pode atingir:
- CPU/memória do executor;
- SSH bastion;
- APIs cloud;
- Git checkout/cache;
- rede;
- próprios alvos.
Limite de plataforma não substitui serial, throttle ou estratégia Ansible, mas cria uma camada global adicional.
Backup do control plane precisa incluir mais que o banco #
A rotina privada faz pg_dump do banco e empacota arquivos necessários para recuperar o ambiente.
Isso inclui material sensível como secrets e configurações cloud.
Por isso o backup é privado e precisa de proteção equivalente às próprias credenciais.
Uma boa política deveria ainda acrescentar, conforme o ambiente:
- criptografia do backup;
- cópia fora do host;
- teste periódico de restore;
- retenção documentada;
- controle de acesso.
O script recuperado prova backup e retenção local; não vou afirmar aqui que todos esses controles adicionais já existem.
O reverse proxy também faz parte do estado #
A stack usa Traefik para TLS e roteamento.
Também adiciona headers para impedir cache da interface.
Essa decisão veio de comportamento de sessão/proxy observado durante a implantação.
Num blueprint público, o hostname real deve desaparecer e virar variável/exemplo fictício.
O que eu publicaria num blueprint #
Não copiaria o Compose real.
Criaria uma versão genérica contendo:
| |
E deixaria AWS/OCI/Kubernetes como módulos opcionais ou documentados.
O que a fonte sustenta #
O repositório privado comprova:
- Semaphore em versão pinada;
- PostgreSQL 16 Alpine;
- health check;
- publicação por Traefik;
- porta de debug em loopback;
- rede interna separada;
- secrets por arquivo;
- mounts AWS/OCI read-only;
- executor com CLIs e clients adicionais;
- retorno para usuário não-root;
- limites de paralelismo/duração;
- rotina de backup com
pg_dumpe retenção local.
Referências específicas de domínio, cliente, contas e operadores foram omitidas.
O principal aprendizado #
Uma interface de automação é parte da infraestrutura que ela controla.
Então eu trato o Semaphore como trataria qualquer control plane:
| |
O playbook pode ser excelente. Se o lugar que executa todos eles for improvisado, o risco só mudou de endereço.
Me chama.
Manda o contexto, os sintomas e o que já foi testado. Bora organizar as evidências antes de sair mexendo.