GitHub Actions para Pipelines de Implantação Self-Hosted
O GitHub Actions está bem documentado para implantações em plataformas PaaS na nuvem — Vercel, Railway, Fly.io e ambientes gerenciados similares possuem integrações nativas. Mas, na prática, uma parcela significativa da infraestrutura de produção roda em instâncias VPS self-hosted ou servidores bare-metal, onde o modelo de implantação é fundamentalmente diferente. Não existe uma API de push-to-deploy. Você precisa acessar o servidor, copiar arquivos e executar comandos. Esta publicação cobre os padrões que usamos para implantar aplicações Docker Compose em um servidor Hetzner self-hosted via GitHub Actions, incluindo gerenciamento de segredos e estratégias de rollback.
Contexto de Infraestrutura
Nosso servidor de produção roda Debian em um Hetzner CX53. O Docker Compose gerencia a stack de serviços, e o Traefik atua como proxy reverso e camada de terminação TLS. As implantações envolvem construir uma imagem Docker, enviá-la para um registro, puxá-la no servidor e reiniciar o serviço afetado. O frontend web é um site estático implantado via rsync em vez de Docker, o que tem um caminho de implantação mais simples, mas o mesmo modelo de acesso.
Os runners do GitHub Actions são hospedados pelo GitHub (não usamos runners self-hosted). Eles se conectam ao nosso servidor via SSH usando uma chave de implantação. Esse é um padrão padrão, mas tem implicações de segurança: a chave de implantação deve ter acesso ao servidor, o que significa que é uma credencial de alto valor que precisa de manuseio cuidadoso tanto no GitHub Secrets quanto no próprio servidor.
Configuração de Implantação via SSH
A chave de implantação é um par de chaves Ed25519 gerado especificamente para CI. A chave privada é armazenada como um GitHub Secret; a chave pública é adicionada ao ~/.ssh/authorized_keys no servidor para o usuário de implantação. Usamos um usuário de sistema dedicado (deploy) com acesso limitado ao shell e propriedade apenas sobre os diretórios nos quais o processo de implantação precisa escrever. Esse usuário não pode executar sudo e não pode acessar os diretórios home de outros usuários.
Conectar-se via SSH em um workflow do GitHub Actions requer carregar a chave privada no agente SSH antes que qualquer comando remoto seja executado. O padrão padrão usa ssh-agent e ssh-add nas etapas do workflow, ou uma ação da comunidade como webfactory/ssh-agent, que lida com o carregamento de chaves e a configuração de hosts conhecidos. Adicionamos a chave do host do servidor aos hosts conhecidos do workflow no momento da configuração para evitar que prompts interativos de verificação de host bloqueiem o pipeline.
Para implantações via rsync — nosso site estático, por exemplo — o workflow compila a saída e então executa rsync -avz --delete via SSH para sincronizar o diretório dist/ com o servidor. O sinalizador --delete garante que os arquivos removidos na origem sejam removidos do destino, o que é importante para sites estáticos onde arquivos obsoletos podem causar comportamento inesperado. A string de conexão usa o usuário de implantação e uma porta SSH não padrão, se aplicável.
Execução Remota com Docker Compose
Implantar um serviço Docker Compose requer uma abordagem diferente. O workflow precisa: compilar a nova imagem (ou puxar uma imagem pré-compilada de um registro) e então no servidor remoto puxar a nova imagem e reiniciar o serviço com tempo de inatividade mínimo.
Usamos um padrão de execução remota em dois passos. O primeiro comando SSH trata do pull: ssh deploy@server "docker pull registry/image:tag". O segundo trata da reinicialização: ssh deploy@server "docker compose -f /opt/stack/docker-compose.yml up -d --no-deps service_name". O sinalizador --no-deps impede que o Docker Compose reinicie serviços dependentes desnecessariamente. Executar pull e reinicialização como comandos separados significa que uma falha no pull não deixa o serviço em um estado parcialmente atualizado.
Para serviços que requerem migrações de banco de dados antes do início da nova versão, adicionamos um terceiro comando SSH que executa a migração dentro da nova imagem de contêiner antes da reinicialização do serviço: docker run --rm --env-file /opt/stack/.env registry/image:tag migrate. As migrações são executadas no banco de dados atual antes de o tráfego mudar para o novo contêiner. Isso assume que as migrações são compatíveis com versões anteriores — um requisito que merece sua própria discussão, mas é um pré-requisito para implantações sem tempo de inatividade, independentemente das suas ferramentas de implantação.
Passamos a tag da imagem como entrada do workflow ou a derivamos do SHA do commit git. Marcar imagens com o SHA do commit em vez de “latest” fornece um registro inequívoco do que está rodando em produção e torna o rollback simples — você pode implantar qualquer tag anterior sem precisar raciocinar sobre o que era “latest” em um determinado momento.
Gerenciamento de Segredos
O GitHub Secrets armazena credenciais que o workflow precisa em tempo de execução: chaves privadas SSH, credenciais de registro, valores de variáveis de ambiente. O GitHub mascara esses valores nos logs do workflow, o que evita exposição acidental na saída da compilação. Os segredos são acessados como variáveis de ambiente nas etapas do workflow: ${{ secrets.SSH_PRIVATE_KEY }}.
Os segredos de aplicação — os valores que vão para o arquivo .env no servidor — são uma preocupação separada. Não armazenamos segredos de aplicação no GitHub Secrets e os injetamos no momento da implantação. Em vez disso, o arquivo .env fica no servidor e é gerenciado independentemente do pipeline de implantação. A implantação não atualiza nem substitui o arquivo env; ela apenas atualiza o código em execução. Isso significa que as alterações nos segredos de aplicação requerem uma etapa manual separada no servidor, o que cria uma barreira intencional em vez de tornar as alterações de variáveis de ambiente parte de cada implantação.
A alternativa — armazenar todos os segredos de aplicação no GitHub e injetá-los durante a implantação — é mais simples de raciocinar, mas concentra a exposição de credenciais. Se sua conta ou repositório do GitHub for comprometido, um invasor com a capacidade de acionar uma execução de workflow teria acesso a todos os segredos de aplicação. Manter segredos no servidor significa que um invasor precisa tanto de acesso ao GitHub quanto de acesso ao servidor para extraí-los.
Estratégias de Rollback
A estratégia de rollback mais direta para uma implantação baseada em Docker é a reimplantação da tag de imagem anterior. Como marcamos imagens com SHAs de commit git, fazer rollback significa reexecutar o workflow de implantação com o SHA do commit anterior como a tag da imagem. Isso pode ser feito revertendo o branch git ou acionando manualmente um workflow dispatch com a tag de destino como parâmetro de entrada.
Para implantações via rsync — o site estático — o rollback é uma reimplantação do artefato de compilação anterior. Retemos artefatos de compilação como artefatos de workflow do GitHub Actions por 30 dias. Para um incidente em produção, baixamos o artefato anterior e fazemos rsync manualmente. Isso é infrequente o suficiente para que um processo manual seja aceitável; automatizá-lo adicionaria complexidade ao workflow que não é justificada pela frequência de rollback.
Um modo de falha comum em pipelines de implantação self-hosted é uma implantação parcial que deixa o serviço em estado inconsistente. A abordagem Docker Compose lida bem com isso porque as reinicializações de contêineres são atômicas do ponto de vista do serviço — ou o novo contêiner inicia com sucesso ou o antigo continua rodando. O cenário mais perigoso é uma migração que roda com sucesso em um banco de dados antes de uma reinicialização de contêiner falhar. Nesse ponto, fazer rollback do código pode não ser seguro se a migração alterou o schema de uma forma que a versão anterior não consegue lidar. Resolvemos isso exigindo que todas as migrações sejam compatíveis com versões anteriores e executando um smoke test contra o novo contêiner antes da etapa final de reinicialização do serviço no workflow.
Observabilidade no Pipeline
O GitHub Actions fornece registro integrado para cada execução de workflow, mas os logs são efêmeros — eles são excluídos após um período de retenção e não são substitutos para observabilidade no nível de aplicação. Tratamos os logs de workflow como informações de diagnóstico para falhas de CI e confiamos no registro do lado do servidor (logs JSON estruturados enviados para um agregador de logs) para investigação de incidentes em produção.
Uma adição que valeu a pena: uma etapa final em cada workflow de implantação que executa uma verificação de integridade contra o serviço implantado. Uma solicitação HTTP simples ao endpoint de integridade do serviço com um timeout e uma asserção de não-200 aciona uma falha do workflow se o serviço não inicializou corretamente. Combinado com uma integração de alertas em falhas de workflow, isso fornece um sinal quase em tempo real de que uma implantação deixou o serviço em estado quebrado, antes que os clientes o encontrem.
Insights Relacionados
Artigos relacionados
Guia Completo de Solução de Problemas do n8n Auto-Hospedado 2025: Corrigindo Tamanho de Dados de Execução e Problemas de Webhook com Traefik
Recuperação de Desastres para Serviços Auto-Hospedados: Nossa Estratégia de Backup
Analytics com Privacidade em Primeiro Lugar: Configurando o Plausible Auto-Hospedado com Google Search Console