CI/CD na sua própria VPS
Um servidor próprio para builds compensa quando os minutos da nuvem não bastam, os builds são lentos ou é preciso acessar recursos internos. Mas o runner executa qualquer código do repositório, e um erro na configuração dele abre o acesso ao servidor de produção e a todos os segredos. Veja como funcionam os runners do GitLab e do GitHub, de quantos recursos eles precisam, como fazer deploy por SSH e onde guardar as chaves.
CI/CD é o build, o teste e a publicação automáticos do código a cada alteração. A descrição do pipeline fica no repositório (.gitlab-ci.yml ou os arquivos em .github/workflows/), e quem a executa é o runner, um programa que pega os jobs do GitLab ou do GitHub e os roda. O runner pode ser o do próprio serviço ou um instalado na sua VPS.
Runners próprios: GitLab e GitHub
O GitLab Runner é instalado a partir do repositório oficial (instruções de instalação) e registrado no projeto ou no grupo com um token das configurações de CI/CD. A escolha principal no registro é o executor. O shell roda os comandos direto no sistema do servidor; o docker, num contêiner novo para cada job. Segundo a documentação do GitLab, o shell não oferece um ambiente limpo para cada build, e os jobs enxergam todo o sistema de arquivos, inclusive o token do runner e o cache de outros projetos. Para a maioria dos projetos, o executor docker é o indicado.
O runner próprio do GitHub Actions é adicionado nas configurações do repositório ou da organização (documentação do GitHub). O uso é gratuito: você paga só o servidor. O runner se conecta sozinho ao GitHub por HTTPS na porta 443, então não é preciso abrir portas de entrada no servidor. Se o runner ficar 30 dias sem atualização, o GitHub deixa de lhe enviar jobs (referência sobre runners próprios).
Uma restrição importante para projetos abertos. O GitHub diz que runners próprios quase nunca devem ser usados com repositórios públicos: qualquer pessoa pode abrir um pull request e executar o próprio código no servidor. Para código público, fique com os runners da nuvem e conecte o seu servidor a repositórios privados.
Por que o build e a produção devem ficar em servidores diferentes
- Carga. O build do front-end ou uma compilação ocupa todos os núcleos por minutos. Se o runner está no servidor do site, o site responde mais devagar nesse tempo.
- Segurança. O runner executa código de qualquer branch, inclusive de uma enviada por um prestador de serviço. Segundo a documentação do GitLab, o executor shell traz risco alto para o servidor e para a rede, e um contêiner em modo privileged dá ao job root completo no servidor. Se no mesmo servidor roda um site com a base de clientes, ela fica ao alcance de qualquer job.
- Espaço em disco. Imagens Docker, cache de dependências e artefatos ocupam dezenas de gigabytes. Quando o disco enche, para não só o build, mas também o site.
Um esquema para uma equipe pequena: uma VPS para o runner, outra para o site em produção e, se preciso, uma terceira para o ambiente de testes (sobre ele, o artigo VPS para desenvolvedores: ambientes de teste). O runner entrega ao servidor de produção só o resultado pronto.
Quantos recursos o build precisa
A referência são os runners de nuvem do GitHub. Segundo a referência dele, o runner Linux padrão para repositórios privados tem 2 núcleos, 8 GB de memória e 14 GB de SSD; para repositórios públicos, 4 núcleos e 16 GB. Se os seus builds cabem nesses recursos, uma VPS igual dá conta tão bem quanto, e com disco rápido e cache preservado costuma ser mais rápida.
- Núcleos. Um build com compilação e testes em paralelo usa todos os núcleos. Se os builds rodam um atrás do outro, 4 núcleos encurtam bem a fila em comparação com dois.
- Memória. O build do front-end, a compilação de Java ou Go e os testes com banco de dados num contêiner pedem 4–8 GB por job. O número de jobs simultâneos é definido pelo parâmetro
concurrentnoconfig.tomldo GitLab Runner (descrição dos parâmetros); multiplique a memória de um job por esse número. - Disco. Instalar dependências significa milhares de arquivos pequenos, e no NVMe isso anda mais rápido. Contrate a partir de 50 GB e limpe o cache do Docker periodicamente:
docker builder pruneedocker image prune. Por que o disco importa está no artigo NVMe ou SSD: faz diferença para um site? - Virtualização. O executor docker precisa de KVM: no OpenVZ, o Docker funciona com limitações ou não funciona (KVM ou OpenVZ: o que escolher).
Pelo catálogo da accsly.io em novembro de 2026, um plano de VPS com 2 núcleos e 4 GB de memória custa na mediana US$ 21 por mês, e um quarto desses planos sai por menos de US$ 12,50. Com 4 núcleos e 8 GB, US$ 40, e um quarto custa menos de US$ 22. Com 8 núcleos e 16 GB, US$ 80. Os preços estão em rublos no catálogo; aqui foram convertidos pelo câmbio de 7 de novembro de 2026 e arredondados. Planos adequados estão nas seleções VPS para runners de CI/CD e VPS para GitLab, se você instala também o próprio GitLab.
Servidor de build cobrado por hora
O runner só é necessário enquanto há builds, normalmente algumas horas por dia útil. Pelo catálogo da accsly.io em novembro de 2026, 173 dos 2.248 provedores com planos de VPS oferecem cobrança por hora, 298 têm API para criar e apagar servidores, e 96 têm as duas coisas. Um plano com 4 núcleos e 8 GB nos provedores com cobrança por hora custa na mediana US$ 51 por mês, cerca de US$ 0,07 por hora. Um servidor que funciona 3 horas em cada um dos 22 dias úteis sai por uns US$ 4,60 em vez do preço do mês.
Para que isso funcione sem trabalho manual, o servidor é criado pela API antes do build e apagado depois. O GitLab Runner sabe escalar assim com plugins para nuvens, e o GitHub recomenda runners efêmeros (ephemeral), que pegam um único job e são removidos. Há um ganho de segurança: o job seguinte não vê rastros do anterior. Quando a cobrança por hora compensa mais que a mensal está no artigo Cobrança por hora: quando ela compensa; os planos, nas seleções VPS com cobrança por hora e VPS com API.
Deploy por SSH com uma chave restrita
O jeito mais simples de levar o build ao servidor de produção é o SSH: rsync dos arquivos ou um comando que baixa a nova versão e reinicia o serviço. Para que a chave do CI não vire a chave do servidor inteiro:
- Um usuário à parte. Crie um usuário
deploysem sudo, com permissão só no diretório do site. Se for preciso reiniciar um serviço, libere no sudoers um único comando específico. - Uma chave à parte. Gere um par de chaves só para o CI e não o use em nenhum outro lugar.
- Restrições no authorized_keys. Antes da chave, acrescente
restrict, que proíbe encaminhamento de portas e terminal, efrom="endereço do runner", para que a chave só funcione a partir do servidor de build. O parâmetrocommand="…"permite que a chave execute apenas um script de deploy (man authorized_keys). - Verificação do servidor. Guarde a impressão digital da chave do servidor de produção numa variável do CI e grave-a no
known_hostsantes de conectar. Desativar a verificação comStrictHostKeyChecking=nonão é aceitável: assim o runner se conecta a qualquer servidor que se passe pelo seu.
Para que o site não pare durante o deploy, a nova versão é iniciada ao lado da antiga e o tráfego é trocado quando ela passa na verificação de saúde; a versão anterior fica disponível para reverter. Como montar isso em um único servidor está explicado em Atualização do servidor sem tempo fora do ar.
A proteção básica do próprio servidor de produção, com login só por chave, firewall e fail2ban, está no artigo Primeiros 10 passos depois de comprar uma VPS. Os registros de login da chave de deploy ajudam a entender o que houve se algo der errado; onde encontrá-los está no artigo Logs e auditoria de acessos no servidor.
Onde guardar os segredos
- Fora do repositório. Senhas do banco de dados, tokens de API e chaves de deploy ficam nas variáveis do CI. No GitLab, a variável pode ser protegida, disponível só para pipelines de branches e tags protegidas, e mascarada, quando o valor aparece como
[MASKED]no log do job. Um valor mascarado precisa ser uma única linha, sem espaços, com pelo menos 8 caracteres (documentação do GitLab). - Um valor por segredo. O GitHub avisa que um segredo em forma de JSON ou YAML é mascarado pior nos logs; guarde cada valor separadamente e não imprima segredos no log do build.
- Segredos diferentes para o ambiente de testes e para a produção. A chave de deploy para produção deve estar disponível só para a branch protegida, e não para qualquer branch de desenvolvedor.
- Tokens de vida curta em vez de permanentes. Para serviços de nuvem, o GitHub recomenda OpenID Connect: o job recebe um token válido durante o build, e não é preciso guardar uma chave permanente.
- Rotação. Troque as chaves quando sair da equipe alguém com acesso às configurações do CI e depois de qualquer vazamento de log de build.
O arquivo .env com as configurações de produção é criado no servidor uma única vez, com permissão 600, e o deploy não o sobrescreve. Assim, nem uma cópia completa do repositório contém as senhas do banco de produção.