CI/CD com GitHub Actions: automatize deploy e testes

Entenda o que é CI/CD na prática e monte dois workflows de GitHub Actions do zero, um rodando lint e teste em cada Pull Request, outro fazendo deploy automático quando o código chega na main.

tecnico devops git 4 min de leitura

Já mostrei aqui como fazer deploy em servidor do zero, entrando por SSH e subindo o código na mão. Isso funciona, mas tem um problema: depender de alguém lembrar de rodar teste antes de subir, ou de repetir os mesmos passos de deploy toda vez sem esquecer nenhum. CI/CD existe justamente pra tirar essa responsabilidade das mãos de uma pessoa e colocar num processo automático.

O que é CI/CD, sem o jargão

CI (Continuous Integration) é o hábito de, toda vez que alguém sobe código, rodar automaticamente uma checagem: os testes passam, o lint não acusa nada, o build funciona. Isso pega problema antes de chegar na branch principal, não depois.

CD (Continuous Deployment ou Continuous Delivery) é o passo seguinte: depois que o código já foi validado, ele vai pro ar sozinho, sem alguém precisando entrar em servidor nenhum. Os dois juntos formam o pipeline: código sobe, é testado automaticamente, e se passar, vai pra produção sem intervenção manual.

GitHub Actions: onde isso roda

O GitHub Actions é a ferramenta de CI/CD já embutida em qualquer repositório do GitHub, sem precisar contratar serviço externo. Ela funciona a partir de arquivos YAML dentro da pasta .github/workflows/, cada arquivo descrevendo um workflow: quando ele roda, e o que ele faz.

Workflow 1: testar todo Pull Request

O primeiro workflow que vale criar é o mais simples: rodar lint e teste automaticamente em todo PR, antes de qualquer merge.

# .github/workflows/ci.yml
name: CI
 
on:
  pull_request:
    branches: [main]
 
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
 
      - uses: actions/setup-node@v4
        with:
          node-version: 22
 
      - run: npm ci
      - run: npm run lint
      - run: npm test

Repara na estrutura: on.pull_request diz quando esse workflow dispara, nesse caso toda vez que um PR é aberto ou atualizado tendo main como destino. jobs.test.steps é a sequência de passos, de cima pra baixo: baixar o código (checkout), instalar o Node na versão certa, instalar dependências com npm ci (mais previsível que npm install em ambiente automatizado, porque respeita exatamente o package-lock.json), e então rodar lint e teste.

Se qualquer um desses passos falhar, o GitHub marca o PR com um X vermelho, direto na tela de revisão, antes mesmo de alguém revisar o código manualmente. Isso evita a situação clássica de aprovar um PR só pra descobrir depois que ele quebra o build.

Workflow 2: deploy automático na main

O segundo workflow cuida do deploy, disparado só quando o código já foi mergeado na main, ou seja, já passou pela revisão e pelo primeiro workflow:

# .github/workflows/deploy.yml
name: Deploy
 
on:
  push:
    branches: [main]
 
jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
 
      - uses: actions/setup-node@v4
        with:
          node-version: 22
 
      - run: npm ci
      - run: npm run build
 
      - name: Deploy via SSH
        uses: appleboy/ssh-action@v1
        with:
          host: ${{ secrets.SERVER_HOST }}
          username: ${{ secrets.SERVER_USER }}
          key: ${{ secrets.SERVER_SSH_KEY }}
          script: |
            cd /var/www/meu-projeto
            git pull origin main
            npm ci
            npm run build
            pm2 restart meu-projeto

O último passo automatiza exatamente aquele processo manual de entrar por SSH: puxar o código novo, reinstalar dependência, buildar, reiniciar o processo. A diferença é que agora ninguém precisa lembrar de fazer isso, acontece sozinho a cada merge.

Secrets: nunca deixe credencial no YAML

Repara que host, usuário e chave SSH não aparecem escritos direto no arquivo, eles vêm de secrets.SERVER_HOST e afins. Isso segue o mesmo princípio que já falei aqui sobre gerenciamento de segredos: essas informações ficam configuradas na aba Settings > Secrets and variables > Actions do repositório no GitHub, criptografadas, e o workflow só referencia o nome delas. O YAML pode ficar público sem expor credencial nenhuma.

Vale ir aos poucos

Não precisa sair automatizando o pipeline inteiro de uma vez. O primeiro workflow (lint e teste em cada PR) já entrega valor sozinho, mesmo mantendo o deploy manual por enquanto, porque pega erro antes de chegar na branch principal. O deploy automático é o passo seguinte, natural, quando o processo manual já estiver bem entendido e repetitivo o suficiente pra valer a pena tirar da mão de alguém.

Fechando

CI/CD não é sobre ferramenta chique, é sobre parar de confiar na memória de alguém pra rodar teste ou repetir passo de deploy sem errar. Uma vez configurado, o pipeline vira parte do fluxo normal do time: PR aberto já mostra se passou nos testes, merge na main já sobe pra produção sozinho. É trabalho de configurar uma vez, que se paga em cada deploy depois disso.