DevOps

Pipelines de CI/CD

CI/CD transforma cada alteração em um artefato imutável e testado, e entrega esse mesmo artefato da mesma maneira todas as vezes. Um pipeline é apenas uma sequência de jobs que é executada sempre que o código se move.

intermediate15 min readUpdated 16 de set. de 2026
.github/workflows/ci.yml
yaml
# .github/workflows/ci.yml
name: ci

on:
  push:
    branches: [main]
  pull_request:

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 22
          cache: npm
      - run: npm ci
      - run: npm run lint
      - run: npm run typecheck
      - run: npm test
Trigger
Push, pull request ou tag
Runner
Uma máquina limpa e descartável
Artefato
Construído uma vez, promovido para todos os lugares
Gates
Lint, typecheck e testes
Delivery
Release automatizada, aprovação manual
Deployment
Automatizado até a produção

Por que importa

O que um pipeline oferece

Cada alteração é verificada

Cada push executa as mesmas etapas de build, lint, typecheck e teste em um ambiente limpo, para que códigos quebrados sejam detectados durante a revisão, e não após um release.

Um artefato, muitos ambientes

O pipeline constrói a imagem ou bundle exatamente uma vez e promove esses mesmos bytes através de staging e produção, garantindo que o que você testou seja o que você entrega.

Releases tornam-se triviais

Deployments são automatizados, observáveis e reversíveis. Fazer um rollback significa redeployar a tag anterior, não reconstruir a partir de uma branch antiga sob pressão.

O panorama completo

As três ideias por trás de CI/CD

Um evento dispara o trabalho, um runner descartável o executa e um artefato imutável é o que se move entre os ambientes.

O trigger

Reagir

Um push, um pull request ou uma tag inicia o pipeline. O evento decide quais jobs serão executados e qual ambiente eles visam.

O runner

Executar

Uma máquina descartável faz o checkout do código e executa cada etapa. Nada sobrevive entre os jobs, o que torna o resultado reproduzível.

O artefato

Entregar

O build produz uma saída imutável, como uma imagem de container ou um bundle, que é promovida sem alterações por todos os ambientes.

HTML5 de uma olhada

As partes móveis de um pipeline

Triggers

on push, pull_request ou tag decidem quando um pipeline começa.

Jobs e steps

Um job roda em um runner; seus steps rodam em ordem e falham rapidamente (fail fast).

Artefatos

A saída do build é armazenada uma vez e baixada por jobs posteriores.

Caching

Caches de dependências e de build reduzem minutos de cada execução.

Matrices

Execute o mesmo job em diferentes versões de Node.js e sistemas operacionais.

Secrets

Credenciais são injetadas em tempo de execução e mascaradas nos logs.

Fluxo

O que um pipeline faz em cada push

A mesma estrutura se aplica quer o pipeline tenha dez linhas ou cem, e quer ele faça deploy para uma VM ou para um cluster Kubernetes.

  1. 1

    Trigger no push ou pull request

    A plataforma envia um evento com o commit, a branch e o autor, e o pipeline seleciona os jobs aplicáveis.

  2. 2

    Checkout do código

    Um runner novo clona o commit exato, para que nada de uma execução anterior vaze para esta.

  3. 3

    Restaurar caches

    Caches de dependências e de build são restaurados pelo hash do lockfile, transformando uma instalação fria em uma instalação quente.

  4. 4

    Instalar dependências

    Uma instalação baseada em lockfile fixa a árvore de dependências para que cada execução e cada máquina resolvam as mesmas versões.

  5. 5

    Lint, typecheck e testes

    Verificações rápidas rodam primeiro e falham o pipeline precocemente. Um gate vermelho impede que o artefato chegue a ser construído.

  6. 6

    Build do artefato ou imagem

    A saída é construída uma vez e enviada para um registry, tagueada com o commit para ser rastreável.

  7. 7

    Deploy na branch main

    Apenas merges para a branch protegida fazem deploy, geralmente atrás de um ambiente com seus próprios secrets e aprovações.

  8. 8

    Executar um smoke check

    Uma requisição rápida contra o novo release confirma que ele está respondendo antes que o tráfego seja totalmente desviado.

  9. 9

    Reportar status

    O commit recebe um check de sucesso ou falha, e um deployment falho é revertido ou mantido com a versão anterior servindo.

O guia completo

Pipelines de CI/CD: Tudo que voce precisa saber

O que CI e CD significam na prática

Continuous integration (integração contínua) é a prática de mesclar o trabalho de cada desenvolvedor em um branch compartilhado com frequência e verificá-lo automaticamente. Cada merge dispara um build, uma passagem de lint e a suíte de testes em uma máquina onde ninguém desenvolve. O objetivo é encontrar problemas de integração em minutos, enquanto a alteração ainda é pequena, em vez de enfrentar um merge doloroso ao final de um branch longo.

Continuous delivery (entrega contínua) estende essa ideia: o branch principal está sempre em um estado passível de release, e o pipeline pode implantá-lo em produção a qualquer momento. O artefato é construído e testado a cada alteração; um humano decide quando fazer o release. Continuous deployment (implantação contínua) remove essa decisão humana, enviando cada alteração que passa pelos critérios de validação diretamente para a produção.

A diferença entre delivery e deployment é o portão de aprovação, e essa é a única diferença. Tudo o que vem antes disso — o build, os testes, o artefato — é idêntico.

Equipes frequentemente afirmam “fazer CI/CD” sem fazer nenhum dos dois. A integração contínua exige que o build seja executado em uma máquina compartilhada e autoritativa, não apenas no laptop antes de um commit. A entrega contínua exige que o artefato produzido pelo pipeline seja aquele que vai para produção, e não algo reconstruído manualmente no momento do release. Se qualquer um desses pontos estiver faltando, o ciclo fica aberto e os benefícios se perdem.

A anatomia de um pipeline

Todo pipeline, desde um arquivo de GitHub Actions de dez linhas até uma configuração empresarial de mil jobs, é construído a partir do mesmo vocabulário.

  • Trigger — o evento que inicia uma execução: um push, um pull request, uma tag, um agendamento ou um disparo manual.
  • Workflow — o arquivo que declara os triggers e jobs. No GitHub Actions, ele fica em .github/workflows/.
  • Job — uma unidade de trabalho que roda em um único runner. Os jobs rodam em paralelo, a menos que você declare dependências com needs.
  • Step — um comando único ou uma action reutilizável dentro de um job. Os steps rodam em ordem e o job para na primeira falha.
  • Runner — a máquina que executa um job. Runners hospedados (hosted) são descartáveis; runners self-hosted são máquinas que você gerencia.
  • Artifact — um arquivo produzido por um job e armazenado para jobs posteriores ou para humanos: um binário, um bundle, um relatório de testes.
  • Cache — um diretório restaurado entre execuções para evitar a repetição de tarefas custosas, como a instalação de dependências.
  • Environment — um alvo nomeado, como staging ou production, com seus próprios secrets, regras de proteção e URL.
  • Secret — um valor criptografado injetado em um job durante a execução e mascarado nos logs.
  • Status check — o resultado de sucesso ou falha reportado ao commit, que pode ser exigido por regras de branch protection.

Uma vez que esses termos estejam claros, qualquer sistema de CI torna-se legível. Jenkins, GitLab CI, CircleCI e GitHub Actions utilizam os mesmos conceitos, apenas com nomenclaturas diferentes.

Um workflow do GitHub Actions, linha por linha

O workflow útil mais simples faz o checkout do código, instala as dependências e executa os testes.

name: ci

on:
  push:
    branches: [main]
  pull_request:

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: 22
          cache: npm

      - run: npm ci
      - run: npm test

on declara os gatilhos. Um push para main e qualquer pull request iniciam uma execução. jobs.test roda em um runner ubuntu-latest limpo. uses executa uma action reutilizável; run executa um comando de shell. npm ci instala exatamente o que o lockfile define, o que torna a execução reproduzível. cache: npm instrui a action de setup a fazer o cache do diretório do npm, utilizando o lockfile como chave.

Adicione concurrency para cancelar execuções superadas, o que economiza tempo e dinheiro em branches ativas:

concurrency:
  group: ci-${{ github.ref }}
  cancel-in-progress: true

Quando um desenvolvedor faz push de três commits seguidos, apenas a execução mais recente sobrevive. As anteriores são canceladas, e o pull request exibe um único status atual em vez de uma fila de status obsoletos.

Build once, deploy o mesmo artefato

O hábito mais valioso em um pipeline é buildar exatamente uma vez. O pipeline compila, testa e empacota a aplicação uma única vez, e cada ambiente, do staging à produção, executa esse mesmo output.

A alternativa — rebuildar para cada ambiente — parece inofensiva, mas não é. Builds diferentes podem resolver versões de dependências distintas, capturar digests de imagens base diferentes ou embutir um NODE_ENV diferente. O staging, então, testa um binário que a produção jamais executará, e o “passou no staging” não prova nada.

Duas regras tornam o build-once prático. Primeiro, tagueie o artefato pelo commit, nunca por um nome mutável como latest:

docker build -t ghcr.io/me/app:$GIT_SHA .
docker push ghcr.io/me/app:$GIT_SHA

Segundo, injete a configuração em runtime. A imagem não deve saber se está rodando em staging ou produção. URLs de banco de dados, feature flags e chaves de API chegam como variáveis de ambiente quando o container inicia. O build é idêntico em todos os lugares; apenas o ambiente difere.

Para promover, referencie o digest imutável em vez da tag, para que mesmo uma imagem retagueada não sofra drift:

kubectl set image deploy/app \
  app=ghcr.io/me/app@sha256:...

Esta é a base que torna os rollbacks triviais. O release anterior é simplesmente o digest anterior, ainda presente no registry, pronto para ser deployado em segundos.

Testes, lint e typecheck como gates

Um gate é uma verificação que deve ser aprovada antes que o pipeline continue. Os gates essenciais são rápidos e baratos: linting, type checking e a suíte de testes unitários. Eles rodam em cada pull request e impedem que o artefato seja buildado quando falham.

Ordene-os do mais rápido para o mais lento. Lint e typecheck geralmente terminam em segundos e capturam uma grande classe de erros. Os testes unitários vêm em seguida. Testes de integração e end-to-end, que são mais lentos, podem rodar em paralelo ou apenas na branch main.

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: 22, cache: npm }
      - run: npm ci
      - run: npm run lint
      - run: npm run typecheck

  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: 22, cache: npm }
      - run: npm ci
      - run: npm test

Dois jobs rodam em paralelo, cada um em seu próprio runner. O lint falha em vinte segundos sem precisar esperar pela suíte de testes. A contrapartida é que ambos os jobs instalam as dependências; um cache compartilhado mantém esse processo barato.

Gates só fazem sentido se forem obrigatórios. No forge, marque as verificações como obrigatórias na proteção de branch para que um pull request não possa ser mergeado enquanto qualquer um deles estiver vermelho.

Matrizes e paralelismo

Uma matriz executa a definição de um job em diversas combinações de inputs. É assim que você testa múltiplas versões do Node e sistemas operacionais sem duplicar o YAML.

jobs:
  test:
    runs-on: ${{ matrix.os }}
    strategy:
      fail-fast: false
      matrix:
        os: [ubuntu-latest, macos-latest]
        node: [20, 22]
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node }}
          cache: npm
      - run: npm ci
      - run: npm test

Isso se expande para quatro jobs. O fail-fast: false permite que os demais terminem mesmo que um falhe, o que é útil quando você quer saber se a falha é específica de uma versão.

O paralelismo não é gratuito. Cada job provisiona um runner e instala as dependências, portanto, uma matriz ampla custa mais tempo e dinheiro do que uma estreita. Teste as versões que você realmente suporta, não todas as versões que existem.

Para suítes grandes, faça o shard dos próprios testes: passe um índice de shard e o total para o test runner para que cada um dos quatro jobs execute um quarto dos testes. A suíte termina em aproximadamente um quarto do tempo.

Secrets e variáveis de ambiente

Secrets são valores criptografados que a pipeline injeta em tempo de execução. Eles devem ficar no armazenamento de secrets da plataforma, nunca no repositório e nunca na imagem.

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - run: ./deploy.sh
        env:
          DATABASE_URL: ${{ secrets.DATABASE_URL }}
          API_KEY: ${{ secrets.API_KEY }}

A plataforma mascara valores de secrets conhecidos nos logs, mas esse mascaramento é baseado em “best-effort”. Ele não consegue capturar um secret que foi transformado, codificado em base64 ou impresso por uma ferramenta que o reformata. A regra de segurança é simples: nunca imprima um secret. Não execute env em um passo de debug, não faça echo de um token e não passe um secret para um comando que registre seus argumentos nos logs.

Limite o escopo dos secrets ao ambiente que precisa deles. Um deploy de staging não tem motivo para ter a senha do banco de dados de produção. No GitHub, secrets de ambiente estão disponíveis apenas para jobs que declaram esse ambiente e podem exigir aprovação prévia.

Para provedores de nuvem, prefira OIDC em vez de chaves de longa duração. A pipeline troca um token de identidade de curta duração por credenciais temporárias, portanto, não há um secret estático para vazar ou rotacionar.

permissions:
  id-token: write
  contents: read

steps:
  - uses: aws-actions/configure-aws-credentials@v4
    with:
      role-to-assume: arn:aws:iam::123456789012:role/deploy
      aws-region: eu-west-1

O token é válido por alguns minutos e limitado a apenas um role. Se ele vazar, o raio de impacto fica restrito a uma única execução.

Um aviso merece destaque: pull requests de forks executam código não confiável. Nunca exponha secrets para esse código. Use pull_request em vez de pull_request_target para qualquer coisa que faça o checkout e o build de código de contribuidores, e exija aprovação antes de executar workflows de contribuidores de primeira viagem.

Ambientes, aprovações e branches protegidas

Um environment é um alvo de deploy nomeado com seus próprios secrets, regras de proteção e URL. É o mecanismo que mantém a produção distinta de todo o resto.

jobs:
  deploy:
    runs-on: ubuntu-latest
    environment:
      name: production
      url: https://app.example.com
    steps:
      - run: ./deploy.sh

Environments podem exigir uma aprovação manual antes que um job seja executado, restringir quais branches podem fazer deploy neles e limitar quanto tempo um deployment aguarda antes de sofrer timeout. É aqui que reside o “botão humano” da entrega contínua: o pipeline roda automaticamente até o portão de segurança, e um revisor o libera.

Branches protegidas complementam os environments. Na branch main, exija um pull request, exija a aprovação de status checks, exija revisão e proíba force pushes. Juntos, eles garantem que a única maneira de chegar à produção seja através do pipeline, que é exatamente o objetivo.

Estratégias de deployment e rollbacks

A maneira como uma nova versão substitui a antiga é uma decisão de design com consequências reais para os usuários.

O Rolling deployment substitui as instâncias aos poucos. A capacidade diminui levemente durante a troca, mas não é necessária infraestrutura extra. É o padrão na maioria das plataformas e o ponto de partida ideal.

O Blue-green deployment executa dois ambientes completos. O tráfego é direcionado para o “blue” enquanto o “green” recebe a nova versão; assim que o “green” está saudável, uma única alteração de roteamento alterna todo o tráfego. O rollback consiste em alternar de volta, o que é quase instantâneo. O custo é manter dois ambientes rodando simultaneamente.

O Canary deployment envia uma pequena fatia do tráfego — um por cento, depois cinco, depois cinquenta — para a nova versão e monitora as taxas de erro e a latência antes de prosseguir. Isso detecta problemas que um health check não consegue, ao custo de um roteamento e monitoramento mais complexos.

Independentemente da estratégia, o deployment deve ser reversível. Como o artefato anterior é imutável e ainda está no registry, um rollback é um redeploy do digest anterior, e não um rebuild:

kubectl rollout undo deploy/app

Dois detalhes tornam os rollbacks seguros. As migrações de banco de dados devem ser retrocompatíveis por pelo menos uma release, para que o código antigo ainda consiga rodar com o novo schema. Além disso, feature flags permitem desativar novos comportamentos sem fazer deploy de nada, o que é a forma mais rápida de rollback que existe.

Registries de artefatos e imagens de container

Um registry de artefatos armazena a saída do build: imagens de container, pacotes npm, binários, tarballs. Ele é o ponto de entrega entre o build e o deploy, e deve ser imutável.

Imagens de container são o artefato mais comum porque carregam seu próprio runtime. Um registry como GitHub Container Registry, Amazon ECR ou Docker Hub armazena camadas, e a pipeline faz o push para ele uma vez por build. Os deploys, então, baixam o digest exato.

- uses: docker/build-push-action@v6
  with:
    context: .
    push: true
    tags: ghcr.io/${{ github.repository }}:${{ github.sha }}
    cache-from: type=gha
    cache-to: type=gha,mode=max

As opções cache-from e cache-to persistem o cache de camadas do Docker entre as execuções, para que uma camada de dependência inalterada não seja reconstruída do zero todas as vezes.

Mantenha uma política de retenção. Registries acumulam gigabytes rapidamente, e deletar imagens antigas faz parte de manter a pipeline rápida. Mantenha pelo menos as últimas versões para que o rollback continue sendo possível.

Verificações de status e proteção de branch

Uma verificação de status (status check) é o veredito do pipeline sobre um commit. O forge a anexa ao pull request, e a proteção de branch decide se ela é apenas consultiva ou obrigatória.

Configure a branch main para exigir:

  • Um pull request antes do merge, com pelo menos uma aprovação.
  • Verificações de status aprovadas, incluindo lint, typecheck e testes.
  • Que a branch esteja atualizada com a main antes do merge, para que as verificações sejam executadas contra o código que será efetivamente integrado.
  • Proibição de force pushes e de deleções.

O resultado é que a branch main está sempre “verde”. Cada commit nela passou pelos mesmos critérios, e cada deploy a partir dela é um artefato comprovadamente estável.

Mantenha a lista de exigências curta. Exigir uma suíte de testes end-to-end lenta em cada pull request faz os desenvolvedores esperarem; exigi-la na merge queue ou na branch main oferece a mesma segurança sem gerar atrito.

Cache e builds incrementais

O cache é a diferença entre um pipeline de dois minutos e um de doze minutos. O princípio é nunca repetir um trabalho cujos inputs não tenham mudado.

O cache mais valioso são as dependências. Utilize o hash do lockfile como chave para que ele seja invalidado exatamente quando as dependências mudarem:

- uses: actions/cache@v4
  with:
    path: ~/.npm
    key: npm-${{ hashFiles('**/package-lock.json') }}
    restore-keys: npm-

restore-keys permite uma correspondência parcial: mesmo quando a chave exata não é encontrada, o cache mais recente é restaurado e atualizado incrementalmente, o que é muito mais rápido do que uma instalação do zero.

As ferramentas de build adicionam seus próprios caches. O build incremental do TypeScript, o pre-bundling de dependências do Vite e o cache de camadas do Docker beneficiam-se de serem persistidos entre as execuções. Para o Docker, utilize o registry ou o backend de cache do provedor de CI em vez do daemon local, que é descartado junto com o runner.

Um cache é uma otimização, nunca uma fonte da verdade. Se um cache estiver corrompido ou desatualizado, a execução ainda deve estar correta. Ferramentas de build que confiam cegamente em um cache podem produzir outputs errados, portanto, defina as chaves de cache de forma conservadora e trate a ausência de cache (cache miss) como algo normal.

Pipelines para monorepos

Um monorepo armazena diversos pacotes em um único repositório. Um pipeline ingênuo reconstrói e testa todos eles a cada alteração, o que se torna mais lento à medida que o repositório cresce.

A solução é a detecção de mudanças. Filtros de caminho e grafos de dependência informam ao pipeline quais pacotes são afetados por um diff, e apenas esses são buildados.

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: pnpm install --frozen-lockfile
      - run: pnpm turbo run test --filter='...[origin/main]'

O filtro seleciona os pacotes alterados desde main, além de tudo que dependa deles. Uma alteração em um utilitário compartilhado dispara seus dependentes; uma alteração em um pacote folha dispara apenas a si mesmo.

Adicione um remote cache para que a saída compilada e os resultados dos testes sejam compartilhados entre máquinas e execuções. Se as entradas de um pacote não foram alteradas, seu build é restaurado em vez de ser recomputado. Em um monorepo, isso geralmente traz um ganho maior do que o cache de dependências.

Mantenha o pipeline rigoroso: uma alteração no lockfile da raiz ou em configurações compartilhadas ainda deve executar a suíte completa. Testes seletivos são uma otimização que nunca deve permitir que um pacote quebrado passe despercebido.

Segurança do pipeline

O pipeline detém credenciais de produção, o que o torna um alvo de alto valor. Trate os arquivos de workflow como código sensível e revise cuidadosamente qualquer alteração neles.

Fixe as actions em uma versão. Uma tag como @v4 é um ponteiro móvel; um commit SHA completo é imutável. Fixar em um SHA é a opção mais rigorosa e evita que uma tag comprometida execute código malicioso em seu pipeline.

Conceda o menor privilégio possível. Defina permissions explicitamente em cada workflow e job, mantendo como padrão o acesso apenas de leitura, e adicione escopos de escrita apenas onde for necessário.

permissions:
  contents: read

O GITHUB_TOKEN padrão geralmente é mais amplo do que qualquer job necessita. Restringi-lo significa que uma etapa comprometida não poderá fazer push de commits ou publicar pacotes.

Prefira OIDC em vez de chaves estáticas, conforme descrito acima. Credenciais de curta duração vinculadas a uma função específica eliminam o segredo de longa duração que os atacantes mais desejam.

Não execute código não confiável com secrets. Pull requests de forks são o vetor clássico. Use pull_request para build e teste, exija aprovação para contribuidores de primeira viagem e nunca faça o checkout e a execução de código de forks em um workflow que tenha acesso a secrets.

Revise actions de terceiros. Uma action é uma dependência que roda com as suas credenciais. Prefira actions do próprio forge ou de publicadores conhecidos, e audite as demais.

Melhores práticas

  • Gere o artefato exatamente uma vez e promova-o através de cada ambiente.
  • Identifique artefatos por commit SHA ou digest, nunca apenas por latest.
  • Injete a configuração em tempo de execução; mantenha o build idêntico em todos os lugares.
  • Execute o lint e o typecheck antes da suíte de testes (que é mais lenta) para que as falhas sejam detectadas rapidamente.
  • Torne as verificações importantes obrigatórias na proteção de branch.
  • Faça o cache de dependências usando o hash do lockfile como chave, com restore keys.
  • Restrinja segredos a ambientes específicos e prefira credenciais OIDC de curta duração.
  • Nunca imprima segredos e nunca os exponha a pull requests não confiáveis.
  • Fixe a versão de actions de terceiros e defina permissões de token de privilégio mínimo.
  • Use concurrency para cancelar execuções superadas.
  • Mantenha o pipeline rápido; um pipeline lento acaba sendo ignorado.
  • Faça com que os rollbacks sejam um redeploy do digest anterior.

Erros comuns

  • Reconstruir a imagem de forma diferente para cada ambiente e chamar isso de promoção.
  • Embutir a configuração do ambiente na imagem durante o build.
  • Usar latest como a única tag e perder a rastreabilidade.
  • Exibir secrets em um passo de debug e assumir que o mascaramento captura tudo.
  • Expor secrets para workflows disparados por pull requests de forks.
  • Referenciar actions de terceiros através de uma branch instável.
  • Deixar as permissões do token padrão totalmente abertas.
  • Marcar cada check opcional como obrigatório e tornar os merges extremamente lentos.
  • Deixar testes flaky ficarem vermelhos até que ninguém mais leia os resultados.
  • Fazer cache agressivamente sem uma chave que invalide com mudanças reais nos inputs.
  • Reconstruir tudo em um monorepo a cada alteração.
  • Não ter um caminho de rollback além de reverter um commit e esperar.

Próximos passos

Um pipeline termina com a implantação de um artefato, portanto, o próximo passo natural são as Plataformas de Nuvem, onde a release, os health checks e os rollbacks realmente acontecem. O artefato geralmente é um container, e o guia de Docker aborda as imagens e os multi-stage builds que o pipeline produz. A maioria dos runners são máquinas Linux, então o guia de Linux explica o shell e as ferramentas das quais seus steps dependem, e o de Node.js fundamenta o runtime e a toolchain que estão sendo construídos.

Na pratica

Quatro jobs, um pipeline

Integração contínua, uma matriz de testes, build de imagem e um deploy com gate — a estrutura completa de um pipeline de produção.

.github/workflows/ci.yml
name: ci

on:
  push:
    branches: [main]
  pull_request:

concurrency:
  group: ci-${{ github.ref }}
  cancel-in-progress: true

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: 22
          cache: npm

      - run: npm ci
      - run: npm run lint
      - run: npm run typecheck
      - run: npm test

Construa uma vez, promova o artefato

Reconstruir por ambiente significa que staging e produção rodam binários diferentes construídos a partir da mesma fonte. Construa uma vez, tagueie por commit e promova esse artefato exato.

Preferir
# build once, tagged by the commit
docker build -t ghcr.io/me/app:$GIT_SHA .
docker push ghcr.io/me/app:$GIT_SHA

# every environment runs the same digest
kubectl set image deploy/app \
  app=ghcr.io/me/app@sha256:...
Evitar
# rebuild separately for each environment
docker build -t app:staging \
  --build-arg NODE_ENV=staging .
docker build -t app:prod \
  --build-arg NODE_ENV=production .

# staging and prod are different binaries

Versões de actions fixadas

Uma action referenciada por uma branch instável pode mudar a qualquer momento, o que representa um risco tanto de confiabilidade quanto de supply-chain.

Preferir
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- uses: docker/build-push-action@v6
Evitar
- uses: actions/checkout@master
- uses: some-org/some-action@main
# runs whatever the branch points at today

Trade-offs

Um pipeline completo vale a configuração?

Um pipeline é código que você mantém. Para qualquer projeto com mais de um contribuidor, ele se paga quase imediatamente.

Strengths

  • Feedback chega cedo

    Um build quebrado é um ajuste de cinco minutos em um pull request, não um rollback à meia-noite. Quanto mais cedo um defeito é detectado, mais barato ele custa.

  • Releases são repetíveis

    As mesmas etapas rodam todas as vezes, então a entrega deixa de depender de quem está no teclado e de qual estado local eles possuem.

  • O artefato é rastreável

    Um commit mapeia para um digest de imagem, que mapeia para um deployment em execução. Responder "o que está em produção" torna-se uma consulta, não uma investigação.

Trade-offs

  • Pipelines lentos são ignorados

    Quando uma execução leva trinta minutos, as pessoas param de acompanhá-la e começam a fazer merge mesmo com falhas. Mantenha os gates rápidos e execute os processos caros em paralelo.

  • Secrets são uma superfície de ataque

    Qualquer workflow que execute código não confiável pode ser enganado para vazar credenciais. Limite o escopo dos tokens, use credenciais OIDC de curta duração e nunca exponha secrets para pull requests de forks.

  • Testes instáveis (flaky) destroem a confiança

    Um teste que falha uma em cada dez vezes ensina a equipe a apertar 'retry' em vez de ler a falha. Coloque testes instáveis em quarentena em vez de tolerá-los.

Perguntas frequentes

Perguntas frequentes

Keep learning

Related topics from the roadmap.

$ comecar a aprender

Pronto para aprender CI/CD Pipelines?

Nosso tutorial interativo te guia por CI/CD Pipelines passo a passo — com quizzes e codigo real que voce pode executar no navegador.