Alternar tema

Guia prático de CI/CD para Next.js: testes e deploy automáticos com GitHub Actions

Easton editorial illustration: rendering-mode selector

Eu olhava os logs rolando no terminal enquanto digitava mecanicamente git pull && npm install && npm run build && pm2 restart. Era o terceiro deploy do dia: de manhã corrigi um pequeno bug, ao meio-dia otimizei uma API e agora tinha ajustado alguns estilos. Eram três servidores, e eu precisava repetir tudo em cada um deles.

Quando terminei no último servidor, já eram sete da noite.

No caminho de volta para casa, fiquei pensando que precisava haver uma maneira melhor. Eu não queria repetir as mesmas operações no servidor depois de cada alteração, muito menos correr o risco de esquecer de reiniciar uma máquina e causar um problema em produção. Até os testes eu esquecia com frequência: terminava uma mudança, queria colocá-la no ar o quanto antes e, quando encontrava um bug depois, precisava recomeçar todo o processo.

Quando conheci CI/CD e GitHub Actions, meu fluxo de trabalho mudou completamente. Hoje, basta fazer push do código para o GitHub; testes, build e deploy acontecem automaticamente. No tempo de tomar um café, a nova versão já está no ar.

Se o deploy manual também está consumindo seu tempo, vou mostrar como criar um fluxo de deploy automatizado para Next.js com GitHub Actions. Veremos desde a configuração básica até um exemplo prático completo, incluindo os problemas que encontrei e como os resolvi.

Por que usar CI/CD

Os problemas do deploy manual

Quando comecei a trabalhar com Next.js, o processo de deploy era assim: eu terminava o código localmente, fazia um teste, acessava o servidor por SSH, executava git pull, instalava as dependências com npm install, gerava o build com npm run build e, por fim, reiniciava o serviço com pm2 restart. Mesmo quando tudo dava certo, o processo levava mais de dez minutos.

Mas nem sempre dava certo.

Uma vez, fiz um deploy em três servidores. Os dois primeiros foram atualizados, mas a conexão SSH do terceiro caiu e eu não percebi. No dia seguinte, usuários disseram que às vezes o site funcionava normalmente e às vezes exibia a versão antiga. O balanceador de carga estava enviando parte das requisições ao servidor desatualizado. Em outra ocasião, fiquei tão empolgado depois de concluir uma alteração que fiz o deploy sem executar os testes. Um bug crítico apareceu em produção e precisei fazer um rollback de emergência.

Há ainda um problema mais traiçoeiro em ambientes com vários servidores. O Next.js gera um novo build ID a cada build. Se cada um dos três servidores gerar seu próprio build, os IDs serão diferentes. Quando o balanceador de carga encaminha as requisições de um usuário para servidores distintos, o Next.js detecta a mudança do ID e força um recarregamento completo de todos os recursos. A experiência do usuário fica péssima.

O que CI/CD realmente resolve

Em resumo, CI/CD automatiza processos repetitivos e sujeitos a erros.

CI (integração contínua) cuida dos testes. A cada push, o pipeline executa automaticamente testes unitários, verificação de tipos e lint. Se algum teste falhar, aquela alteração não será implantada: o fluxo apresenta o erro para que você o corrija. Isso evita que código problemático chegue à produção.

CD (deploy contínuo) cuida do build e do deploy. Os testes passaram? Então o build começa automaticamente e, quando termina, a aplicação é implantada no servidor sem nenhuma intervenção manual.

Na prática, meu fluxo atual é: escrever o código localmente → enviar para o GitHub → testar automaticamente → gerar o build automaticamente → fazer o deploy automaticamente. Do push até a publicação leva cerca de cinco minutos, sem que eu precise acompanhar o terminal. Posso buscar um copo d’água e, quando volto, a nova versão já está em produção.

Outro benefício é a rastreabilidade. Cada deploy mantém um registro completo: qual commit o iniciou, qual foi o resultado dos testes e em quais servidores a aplicação foi implantada. Se algo der errado, é muito mais fácil localizar a causa.

Configuração básica do GitHub Actions

Primeiro, entenda como ele funciona

Quando comecei a usar o GitHub Actions, conceitos como workflow, job e step pareciam confusos. Na verdade, são simples:

Um workflow (fluxo de trabalho) é um processo completo de automação. Por exemplo, “testar + gerar o build + fazer o deploy” pode ser um workflow. Ele é definido em um arquivo YAML dentro do diretório .github/workflows do projeto.

Um job (trabalho) é uma tarefa independente dentro do workflow. Você pode ter, por exemplo, um job de “teste” e outro de “deploy”. Os jobs podem ser executados em paralelo ou ter dependências que determinam sua ordem.

Um step (etapa) é uma operação específica dentro de um job, como baixar o código, instalar dependências ou executar os testes.

Os gatilhos também são flexíveis. Você pode executar o workflow a cada push para a branch main, somente ao abrir um Pull Request ou até em um horário programado todos os dias.

Crie seu primeiro workflow

Na raiz do projeto, crie o diretório .github/workflows e, dentro dele, um arquivo chamado ci-cd.yml. A configuração mais básica é esta:

name: CI/CD Pipeline

# Executar quando houver um push para a branch main
on:
  push:
    branches: [main]

jobs:
  build:
    # Executar na versão mais recente do Ubuntu
    runs-on: ubuntu-latest

    steps:
      # Etapa 1: baixar o código
      - uses: actions/checkout@v4

      # Etapa 2: configurar o ambiente do Node.js
      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '18'

      # Etapa 3: instalar as dependências
      - name: Install dependencies
        run: npm ci

      # Etapa 4: gerar o build do projeto
      - name: Build
        run: npm run build

Essa configuração significa que, a cada push para a branch main, o GitHub Actions executará em um sistema Ubuntu as etapas de baixar o código, configurar o Node.js, instalar as dependências e gerar o build.

Depois de enviar o arquivo para o GitHub, abra a aba “Actions” do repositório para acompanhar a execução do workflow. Confesso que ver aquele primeiro indicador verde de sucesso dá uma boa sensação.

Proteja informações confidenciais com Secrets

Um deploy para servidor costuma exigir informações confidenciais, como IP, chave SSH e token de API. Nunca coloque esses dados diretamente no arquivo YAML, pois eles ficariam expostos ao serem enviados para o GitHub.

O correto é usar o recurso Secrets do GitHub. No repositório, abra Settings → Secrets and variables → Actions e clique em “New repository secret”. Você pode, por exemplo, criar um secret chamado SERVER_HOST cujo valor seja o endereço IP do servidor.

No workflow, use ${{ secrets.SERVER_HOST }} para fazer referência a ele. O GitHub substitui a expressão pelo valor real e ainda o mascara nos logs para evitar vazamentos.

Criando um fluxo de testes automatizados

Configuração do ambiente de testes

Minha regra para testes é simples: tudo que puder ser automatizado não deve depender de uma ação manual. Meu fluxo inclui lint com ESLint, verificação de tipos com TypeScript e testes unitários com Jest. Juntos, eles impedem que a maioria dos problemas avance.

Veja a configuração completa do job de testes:

jobs:
  test:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '18'
          cache: 'npm'  # Armazena dependências npm em cache para acelerar builds futuros

      - name: Install dependencies
        run: npm ci

      - name: Lint check
        run: npm run lint

      - name: Type check
        run: npm run type-check

      - name: Run tests
        run: npm run test -- --coverage

Aqui há um pequeno truque: cache: 'npm' armazena as dependências npm em cache. Nas execuções seguintes, não será necessário baixar tudo de novo, o que pode economizar vários minutos.

Algumas maneiras de acelerar os testes

No começo, meu fluxo de testes levava mais de dez minutos por execução. Depois de algumas otimizações, esse tempo caiu para três minutos.

O primeiro recurso é o cache. Além do cache npm já mencionado, também é possível armazenar o cache de build do Next.js:

- name: Cache Next.js build
  uses: actions/cache@v3
  with:
    path: |
      ~/.npm
      .next/cache
    key: ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json') }}

Assim, o Next.js não precisa reconstruir todas as páginas em cada execução, o que reduz bastante o tempo.

O segundo recurso é a execução paralela. Se os testes não dependem uns dos outros, você pode separá-los em vários jobs:

jobs:
  lint:
    runs-on: ubuntu-latest
    steps: [...]  # Executa apenas o lint

  test:
    runs-on: ubuntu-latest
    steps: [...]  # Executa apenas os testes unitários

  type-check:
    runs-on: ubuntu-latest
    steps: [...]  # Executa apenas a verificação de tipos

Os três jobs começam ao mesmo tempo. Assim, o tempo total corresponde à duração do job mais lento, e não à soma dos três.

Problemas que encontrei

Em uma ocasião, os testes passavam localmente, mas falhavam no GitHub Actions. Depois de bastante investigação, descobri que, ao baixar o código de um Pull Request, o GitHub Actions mescla por padrão a branch do PR com a branch de destino e cria um commit temporário. Como esse commit não existe localmente, ele pode causar comportamentos inesperados.

Uma solução é adicionar um parâmetro ao checkout:

- uses: actions/checkout@v4
  with:
    ref: ${{ github.head_ref }}  # Usa o código original da branch do PR

Outro problema é o timeout dos testes. Alguns testes E2E são lentos, e o limite padrão pode não ser suficiente. Você pode configurar um tempo maior no job:

jobs:
  test:
    runs-on: ubuntu-latest
    timeout-minutes: 15  # O padrão é 6 minutos

Configurando o deploy automático

Deploy na Vercel: a opção mais simples

Se seu projeto está hospedado na Vercel, você praticamente não precisa configurar nada. Vercel e GitHub têm integração nativa: depois de conectar o repositório ao projeto da Vercel, cada push gera um deploy automático.

Para ter um controle mais preciso, como permitir o deploy somente depois que os testes passarem, você pode usar uma GitHub Action oficial da Vercel:

jobs:
  deploy:
    runs-on: ubuntu-latest
    needs: test  # Aguarda a conclusão do job de testes
    if: github.ref == 'refs/heads/main'  # Faz deploy somente na branch main

    steps:
      - uses: actions/checkout@v4

      - name: Deploy to Vercel
        uses: amondnet/vercel-action@v25
        with:
          vercel-token: ${{ secrets.VERCEL_TOKEN }}
          vercel-org-id: ${{ secrets.VERCEL_ORG_ID }}
          vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }}
          vercel-args: '--prod'  # Faz deploy no ambiente de produção

Você precisa gerar um token no painel da Vercel, localizar o org ID e o project ID nas configurações do projeto e adicionar os três valores aos GitHub Secrets.

A Vercel também cria automaticamente um ambiente de preview para cada PR. Ao abrir um PR, ela implanta um ambiente temporário e fornece um link para você conferir as alterações diretamente. É muito prático.

Deploy em servidor próprio: mais flexível, porém um pouco mais complexo

Hospedo meus projetos em servidores próprios porque quero ter mais controle. Nesse caso, é necessário configurar uma conexão SSH para que o GitHub Actions execute os comandos de deploy no servidor.

Primeiro, gere um par de chaves SSH localmente:

ssh-keygen -t ed25519 -C "github-actions"

Adicione a chave pública ao arquivo ~/.ssh/authorized_keys do servidor e a chave privada aos GitHub Secrets, por exemplo com o nome SSH_PRIVATE_KEY.

Depois, configure o job de deploy:

jobs:
  deploy:
    runs-on: ubuntu-latest
    needs: test
    if: github.ref == 'refs/heads/main'

    steps:
      - name: Deploy to server
        uses: appleboy/ssh-action@master
        with:
          host: ${{ secrets.SERVER_HOST }}
          username: ${{ secrets.SERVER_USER }}
          key: ${{ secrets.SSH_PRIVATE_KEY }}
          script: |
            cd /var/www/my-nextjs-app
            git pull origin main
            npm install
            npm run build
            pm2 restart nextjs-app

Essa configuração abre uma conexão SSH com o servidor, baixa o código mais recente, instala as dependências, gera o build e reinicia a aplicação. Todo o processo é automatizado, sem que você precise acessar o servidor manualmente.

Resolva a divergência de build ID entre vários servidores

Se, como eu, você faz deploy em vários servidores atrás de um balanceador de carga, há um detalhe essencial: o build ID precisa ser o mesmo em todas as máquinas. Caso contrário, o usuário pode sofrer recarregamentos constantes.

A solução é gerar o build em um único lugar e distribuir os artefatos a todos os servidores. Faço assim:

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '18'
      - run: npm ci
      - run: npm run build

      # Empacota e envia os artefatos de build
      - name: Upload build artifacts
        uses: actions/upload-artifact@v3
        with:
          name: next-build
          path: |
            .next
            public

  deploy:
    runs-on: ubuntu-latest
    needs: build
    strategy:
      matrix:
        server: [server1, server2, server3]  # Vários servidores

    steps:
      # Baixa os artefatos de build
      - name: Download build artifacts
        uses: actions/download-artifact@v3
        with:
          name: next-build

      # Faz o deploy no servidor correspondente
      - name: Deploy to ${{ matrix.server }}
        uses: appleboy/scp-action@master
        with:
          host: ${{ secrets[format('{0}_HOST', matrix.server)] }}
          username: ${{ secrets.SERVER_USER }}
          key: ${{ secrets.SSH_PRIVATE_KEY }}
          source: ".next,public"
          target: "/var/www/my-nextjs-app"

O build é gerado uma única vez e o mesmo conjunto de artefatos é distribuído aos três servidores. Assim, todos terão exatamente o mesmo build ID.

Otimizações e boas práticas

O que fazer quando o build falha

A automação é ótima, mas falhas ainda podem acontecer. Você precisa saber quando um build ou deploy falhar; caso contrário, pode enviar o código, ter uma falha na publicação e nem perceber.

Minha solução é configurar notificações de erro. Elas podem ser enviadas por e-mail, Slack ou DingTalk. Veja um exemplo com o Slack:

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      [...etapas de deploy...]

      # Se o deploy falhar, envia uma notificação ao Slack
      - name: Notify on failure
        if: failure()
        uses: 8398a7/action-slack@v3
        with:
          status: ${{ job.status }}
          text: 'O deploy falhou! Verifique o que aconteceu.'
          webhook_url: ${{ secrets.SLACK_WEBHOOK }}
          channel: '#deploy-notifications'

Se o deploy falhar, o Slack receberá uma notificação e a equipe poderá agir imediatamente.

Estratégia de branches: separe desenvolvimento e produção

Em projetos reais, uso branches diferentes para ambientes diferentes. A branch develop faz deploy no ambiente de testes, enquanto a branch main publica em produção. A configuração é assim:

on:
  push:
    branches:
      - main      # Ambiente de produção
      - develop   # Ambiente de testes

jobs:
  deploy-staging:
    if: github.ref == 'refs/heads/develop'
    runs-on: ubuntu-latest
    steps:
      - [...deploy no ambiente de testes...]

  deploy-production:
    if: github.ref == 'refs/heads/main'
    runs-on: ubuntu-latest
    steps:
      - [...deploy no ambiente de produção...]

Durante o desenvolvimento, as alterações são enviadas para develop e implantadas automaticamente no ambiente de testes. Depois da validação, a branch é mesclada em main, iniciando o deploy automático em produção.

Algumas equipes também exigem aprovação manual antes de um deploy em produção. O GitHub Actions oferece esse recurso: adicione environment ao job e configure os aprovadores nas definições do repositório.

jobs:
  deploy-production:
    runs-on: ubuntu-latest
    environment:
      name: production  # Ambiente que exige aprovação
    steps: [...]

Assim, cada deploy em produção é pausado até que um aprovador clique em “aprovar”. Para projetos importantes, essa camada adicional de proteção é útil.

Mecanismo de rollback

Mesmo com testes e aprovação, ainda podem surgir problemas em produção. Nesse caso, é preciso voltar rapidamente à versão anterior.

Uma estratégia simples é criar uma tag para cada deploy e manter os artefatos das versões mais recentes. Quando precisar fazer rollback, inicie manualmente um workflow e informe a tag desejada:

on:
  workflow_dispatch:  # Gatilho manual
    inputs:
      tag:
        description: 'Tag para a qual fazer rollback'
        required: true

jobs:
  rollback:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          ref: ${{ github.event.inputs.tag }}  # Usa a tag informada

      - name: Deploy
        [...fluxo normal de deploy...]

Na página do GitHub Actions, clique em “Run workflow”, informe a tag e execute o rollback rapidamente.

Gerenciamento de variáveis de ambiente

Projetos Next.js frequentemente precisam de variáveis de ambiente, como o endereço da API ou a string de conexão do banco de dados. Nunca fixe esses valores diretamente no código nem os envie para o repositório Git.

As boas práticas são:

  1. Armazenar informações confidenciais nos GitHub Secrets
  2. Injetá-las como variáveis de ambiente no workflow
  3. Fazer o Next.js ler as variáveis durante o build
- name: Build
  run: npm run build
  env:
    NEXT_PUBLIC_API_URL: ${{ secrets.API_URL }}
    DATABASE_URL: ${{ secrets.DATABASE_URL }}

Dessa forma, cada ambiente pode usar valores diferentes sem exigir nenhuma alteração no código.

Caso prático: exemplo de configuração completa

Depois de toda essa explicação, veja uma configuração completa. Ela inclui testes, build e deploy e pode servir como base para seu projeto:

name: Next.js CI/CD

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

jobs:
  # Job 1: verificações de código e testes
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '18'
          cache: 'npm'

      - name: Install dependencies
        run: npm ci

      - name: Lint check
        run: npm run lint

      - name: Type check
        run: npm run type-check

      - name: Run tests
        run: npm run test -- --coverage

  # Job 2: build
  build:
    runs-on: ubuntu-latest
    needs: test  # Só gera o build se os testes passarem
    if: github.event_name == 'push'  # Gera o build somente em eventos de push, não em PRs

    steps:
      - uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '18'

      - name: Cache dependencies and build
        uses: actions/cache@v3
        with:
          path: |
            ~/.npm
            .next/cache
          key: ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json') }}

      - name: Install dependencies
        run: npm ci

      - name: Build
        run: npm run build
        env:
          NEXT_PUBLIC_API_URL: ${{ secrets.API_URL }}

      # Envia os artefatos de build usados no deploy
      - name: Upload build artifacts
        uses: actions/upload-artifact@v3
        with:
          name: next-build
          path: |
            .next
            public
            package.json

  # Job 3: deploy no ambiente de testes
  deploy-staging:
    runs-on: ubuntu-latest
    needs: build
    if: github.ref == 'refs/heads/develop'

    steps:
      - name: Download build artifacts
        uses: actions/download-artifact@v3
        with:
          name: next-build

      - name: Deploy to staging server
        uses: appleboy/ssh-action@master
        with:
          host: ${{ secrets.STAGING_HOST }}
          username: ${{ secrets.SERVER_USER }}
          key: ${{ secrets.SSH_PRIVATE_KEY }}
          script: |
            cd /var/www/staging
            pm2 stop nextjs-app || true
            rm -rf .next public
            pm2 start npm --name "nextjs-app" -- start
            pm2 save

      - name: Notify Slack
        if: always()
        uses: 8398a7/action-slack@v3
        with:
          status: ${{ job.status }}
          text: 'Deploy no ambiente de testes: ${{ job.status == "success" && "sucesso" || "falha" }}'
          webhook_url: ${{ secrets.SLACK_WEBHOOK }}

  # Job 4: deploy no ambiente de produção
  deploy-production:
    runs-on: ubuntu-latest
    needs: build
    if: github.ref == 'refs/heads/main'
    environment:
      name: production  # Exige aprovação manual

    steps:
      - name: Download build artifacts
        uses: actions/download-artifact@v3
        with:
          name: next-build

      - name: Deploy to production server
        uses: appleboy/ssh-action@master
        with:
          host: ${{ secrets.PROD_HOST }}
          username: ${{ secrets.SERVER_USER }}
          key: ${{ secrets.SSH_PRIVATE_KEY }}
          script: |
            cd /var/www/production
            pm2 stop nextjs-app || true
            rm -rf .next public
            pm2 start npm --name "nextjs-app" -- start
            pm2 save

      - name: Notify Slack
        if: always()
        uses: 8398a7/action-slack@v3
        with:
          status: ${{ job.status }}
          text: 'Deploy em produção: ${{ job.status == "success" && "sucesso" || "falha" }}'
          webhook_url: ${{ secrets.SLACK_WEBHOOK }}

Nesse fluxo, um PR executa somente os testes, sem gerar build nem fazer deploy. Um push para develop implanta automaticamente no ambiente de testes; um push para main implanta em produção, após aprovação manual. Cada deploy envia uma notificação ao Slack, inclusive em caso de falha.

Para usar esse workflow, configure estes Secrets no repositório do GitHub:

  • API_URL: endereço da API
  • STAGING_HOST / PROD_HOST: endereço dos servidores de testes e produção
  • SERVER_USER: usuário SSH
  • SSH_PRIVATE_KEY: chave SSH privada
  • SLACK_WEBHOOK: webhook do Slack (opcional)

Depois de configurar esses valores, todo o fluxo estará pronto. Você só precisa escrever o código e fazer o push; o restante acontece automaticamente.

Conclusão

A transição do deploy manual para a automação mudou de verdade minha experiência de desenvolvimento. Não preciso mais repetir comandos no servidor após cada alteração, preocupar-me por ter esquecido os testes nem ficar olhando o terminal enquanto o build termina.

Configurar um pipeline de CI/CD com GitHub Actions pode exigir algum tempo no início, mas vale a pena. Depois de pronto, ele continua trazendo benefícios. Comece com a configuração mais simples, faça os testes e o build funcionarem e, aos poucos, adicione deploy, notificações e rollback.

Hoje, olhando para trás, nem sei como eu conseguia conviver com deploys manuais. Se você ainda faz tudo à mão, recomendo reservar um tempo para automatizar. Faça o push, tome um café e volte para encontrar a nova versão no ar — depois de experimentar, é difícil querer voltar ao processo antigo.

Comece agora pelo seu projeto Next.js: crie o primeiro arquivo de workflow e veja o GitHub Actions entrar em ação. Quando isso acontecer, você entenderá exatamente o que quero dizer.

Fluxo completo de configuração de CI/CD para Next.js

Etapas completas, desde a criação do workflow do GitHub Actions até a configuração de testes, build e deploy.

⏱️ Estimated time: 2 hr

  1. 1

    Step 1: Criar o workflow do GitHub Actions

    Crie `.github/workflows/deploy.yml`:
    ```yaml
    name: Deploy

    on:
    push:
    branches: [main]

    jobs:
    build-and-deploy:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v3
    - uses: actions/setup-node@v3
    with:
    node-version: '18'
    - run: npm install
    - run: npm run build
    - run: npm test
    ```

    Pontos principais:
    • Gatilho: push para a branch main
    • Ambiente de execução: ubuntu-latest
    • Etapas: checkout → setup-node → install → build → test
  2. 2

    Step 2: Configurar as etapas de teste

    Adicione os testes:
    ```yaml
    - name: Run tests
    run: npm test

    - name: Type check
    run: npm run type-check

    - name: Lint
    run: npm run lint
    ```

    Pontos principais:
    • Uma falha nos testes impede o deploy
    • A verificação de tipos garante a segurança dos tipos
    • O lint ajuda a manter a qualidade do código

    Vantagens:
    • Evita colocar código problemático em produção
    • As verificações são automáticas, portanto você não se esquece de executá-las
  3. 3

    Step 3: Configurar a etapa de build

    Configuração do build:
    ```yaml
    - name: Build
    run: npm run build
    env:
    NEXT_PUBLIC_API_URL: ${{ secrets.NEXT_PUBLIC_API_URL }}
    ```

    Otimização com cache:
    ```yaml
    - name: Cache dependencies
    uses: actions/cache@v3
    with:
    path: ~/.npm
    key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
    ```

    Pontos principais:
    • Configure as variáveis de ambiente
    • Use cache para acelerar o build
    • Verifique a saída do build
  4. 4

    Step 4: Configurar a etapa de deploy

    Deploy por SSH:
    ```yaml
    - name: Deploy to server
    uses: appleboy/ssh-action@master
    with:
    host: ${{ secrets.HOST }}
    username: ${{ secrets.USERNAME }}
    key: ${{ secrets.SSH_KEY }}
    script: |
    cd /path/to/app
    git pull
    npm install
    npm run build
    pm2 restart app
    ```

    Deploy em vários servidores:
    ```yaml
    - name: Deploy to servers
    uses: appleboy/ssh-action@master
    with:
    host: ${{ secrets.HOSTS }}
    username: ${{ secrets.USERNAME }}
    key: ${{ secrets.SSH_KEY }}
    script: |
    cd /path/to/app
    git pull
    npm install
    # Use o artefato gerado no GitHub Actions
    pm2 restart app
    ```

    Pontos principais:
    • Use autenticação por chave SSH
    • Mantenha um único build ID, gerando o build no GitHub Actions
    • Evite build IDs diferentes entre servidores

FAQ

Por que usar CI/CD?
Problemas do deploy manual:
• Repetir as mesmas operações no servidor a cada alteração
• Esquecer de reiniciar o servidor
• Esquecer de executar os testes
• Cometer erros ao fazer deploy em vários servidores
• Sofrer recarregamentos completos por causa de build IDs diferentes

Vantagens de CI/CD:
• Testes, build e deploy automatizados
• Publicação automática após o push do código
• Menos erros de deploy manual
• Um único build ID
• Maior produtividade no desenvolvimento

Casos reais:
• Em um deploy para três servidores, os dois primeiros foram atualizados, mas a conexão SSH do terceiro caiu sem que ninguém percebesse
• O balanceador de carga enviou requisições ao servidor desatualizado, e usuários viram a versão antiga
• O código foi implantado sem testes e um bug crítico apareceu em produção

Solução: automatizar todo o fluxo com GitHub Actions.
Como configurar o GitHub Actions?
Crie `.github/workflows/deploy.yml`:
```yaml
name: Deploy

on:
push:
branches: [main]

jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: '18'
- run: npm install
- run: npm run build
- run: npm test
```

Pontos principais:
• Gatilho: push para a branch main
• Ambiente de execução: ubuntu-latest
• Etapas: checkout → setup-node → install → build → test

Configure os Secrets:
• Adicione-os em Settings → Secrets no repositório do GitHub
• Por exemplo, HOST, USERNAME e SSH_KEY
• Eles serão usados no deploy por SSH

Recomendação: comece pela configuração mais simples, faça testes e build funcionarem e só então adicione o deploy.
Como evitar build IDs diferentes no deploy em vários servidores?
Problema: quando cada servidor gera seu próprio build, os IDs ficam diferentes e o balanceamento de carga pode provocar um recarregamento completo.

Solução: usar um único build ID.

Gere o build no GitHub Actions:
```yaml
- name: Build
run: npm run build

- name: Deploy to servers
uses: appleboy/ssh-action@master
with:
script: |
cd /path/to/app
git pull
# Use o artefato gerado no GitHub Actions
pm2 restart app
```

Ou use artefatos de build:
```yaml
- name: Upload build artifacts
uses: actions/upload-artifact@v3
with:
name: build
path: .next

- name: Deploy to servers
uses: appleboy/ssh-action@master
with:
script: |
# Baixe o artefato de build
# Faça o deploy nos servidores
```

Pontos principais:
• Gere um único build no GitHub Actions
• Distribua os mesmos artefatos para todos os servidores
• Não gere um build separado em cada servidor

Vantagens:
• Build IDs idênticos
• Sem recarregamentos completos causados por divergência
• Melhor experiência para o usuário
Como configurar as etapas de teste?
Adicione os testes:
```yaml
- name: Run tests
run: npm test

- name: Type check
run: npm run type-check

- name: Lint
run: npm run lint
```

Pontos principais:
• Uma falha nos testes impede o deploy
• A verificação de tipos garante a segurança dos tipos
• O lint ajuda a manter a qualidade do código

Vantagens:
• Evita colocar código problemático em produção
• As verificações são automáticas, portanto você não se esquece de executá-las
• Melhora a qualidade do código

Recomendações:
• Comece pelos testes mais simples
• Aumente gradualmente a cobertura
• Melhore continuamente a qualidade dos testes
Como configurar notificações de deploy?
Notificação no Slack:
```yaml
- name: Notify Slack
uses: 8398a7/action-slack@v3
with:
status: ${{ job.status }}
text: 'Deployment completed'
webhook_url: ${{ secrets.SLACK_WEBHOOK }}
```

Notificação por e-mail:
```yaml
- name: Send email
uses: dawidd6/action-send-mail@v3
with:
to: [email protected]
subject: 'Deployment completed'
body: 'Deployment to production completed successfully'
```

Pontos principais:
• Notifique tanto o sucesso quanto a falha do deploy
• Inclua informações como versão e horário
• Avise a equipe rapidamente

Recomendações:
• Use o Slack para notificações em tempo real
• Use o e-mail como alternativa
• Inclua os detalhes do deploy
Quais são as boas práticas de CI/CD?
Implementação gradual:
1. Primeiro, faça testes e build funcionarem
2. Depois, adicione o deploy aos poucos
3. Por fim, acrescente notificações e rollback

Não tente concluir tudo de uma só vez. Coloque o fluxo básico em funcionamento e evolua-o gradualmente.

Melhoria contínua:
• Confira os logs depois de cada deploy
• Ajuste a configuração conforme a realidade do projeto
• Otimize o tempo de build
• Aumente a cobertura de testes

Indicadores principais:
• Tempo de build
• Taxa de aprovação dos testes
• Taxa de sucesso dos deploys
• Quantidade de rollbacks

Recomendações:
• Comece pela configuração mais simples
• Melhore continuamente
• Colabore com a equipe

Lembre-se: CI/CD não é uma tarefa pontual, mas um processo contínuo.

16 min de leitura · Publicado em: 20 dez 2025 · Atualizado em: 8 set 2026

Comentários

Entre com GitHub para comentar

Easton BlogEaston Blog