GitHub Actions: build, testar em container e publicar imagem no GHCR
Por John Pires · 19 de março de 2026

O que é e por que importa
GitHub Actions é o mecanismo de CI/CD integrado ao GitHub que permite automatizar pipelines diretamente no repositório. Para quem está começando, pensar em Actions como uma linha de montagem programável ajuda: você define etapas que acontecem automaticamente quando um evento dispara (push, pull request, tag, etc.).
Publicar imagens em um registro como o GHCR (GitHub Container Registry) fecha o ciclo de entrega: código no repositório vira imagem Docker disponível para deploy. Isso importa porque facilita testes reprodutíveis, integrações com orquestradores e distribuição entre times.
Do ponto de vista prático, vamos focar em três atividades concretas e encadeadas: build da imagem, executar testes dentro de um container construído e publicar a imagem no GHCR se os testes passarem.
Exigir que os testes sejam executados em um container idêntico ao que será usado em produção reduz falhas por diferenças de ambiente. A pipeline que mostrarei prioriza isso: build para teste (sem push), execução de testes no container carregado localmente, e push final para GHCR.
Analogia operacional
Pense na pipeline como uma fábrica: montar (build), inspecionar (testar) e enviar (publicar). Se a inspeção falhar, a remessa é rejeitada. Esse fluxo evita enviar imagens quebradas para produção.
O que você precisa ter pronto
Antes de começar, assegure-se de ter: uma conta GitHub, repositório com código (ex.: Node.js/Express), GITHUB_TOKEN padrão em ações (ou PAT com permissões se necessário), e permissões de escrita em packages para publicar no GHCR.
Conceitos fundamentais com analogias
Vamos mapear conceitos técnicos para analogias simples. Imagem Docker é uma caixa empacotada com a aplicação e dependências; container é a caixa sendo usada por um cliente — isolada, com recursos definidos.
Build é o processo de criar essa caixa; test é abrir a caixa e verificar se tudo funciona; push é enviar a caixa ao armazém (GHCR) para que outros possam consumi-la.
O GitHub Actions roda os passos em runners (máquinas virtuais hospedadas ou self-hosted). Cada job pode rodar em um runner diferente; para operações com Docker, usamos ações que instalam e configuram o docker/buildx, carregando imagens para permitir testes locais dentro do workflow.
Por que testar no container?
Testar no container garante que a mesma imagem usada em produção passou pelos testes. Sem isso, o build e os testes podem usar imagens/ambientes diferentes — resultado: "funciona na minha máquina" continua acontecendo.
Autenticação no GHCR
O GHCR aceita o GITHUB_TOKEN para publish em muitos casos. Use secrets.GITHUB_TOKEN ou um PAT com scope "write:packages" se necessário. No workflow fornecemos exemplos que usam o GITHUB_TOKEN, que é mais simples para repositórios GitHub.
Passo a passo com código comentado
Aqui vamos construir um fluxo mínimo: Dockerfile para a aplicação, script de teste que roda dentro do container, e workflow GitHub Actions que constrói, testa e publica.
Comece com um projeto Node.js simples que tem testes reais (npm test rodando mocha/jest). O Dockerfile precisa expor como rodar testes no container (com a mesma imagem que será publicada).
Dockerfile (o que faz)
O Dockerfile abaixo cria uma imagem baseada em node:18, instala dependências e define comandos para rodar a aplicação e os testes. Não é um "hello world": contém dependências reais e comando test.
FROM node:18-alpine
# diretório de trabalho
WORKDIR /app
# copia package files primeiro para aproveitar cache
COPY package.json package-lock.json ./
RUN npm ci --silent
# copia o código da aplicação
COPY . .
# build se necessário (ex: transpilar Typescript)
# RUN npm run build
# porta exposta para runtime, não usada nos testes
EXPOSE 3000
# comando padrão (pode ser substituído no container)
CMD ["node", "src/index.js"]
Depois de buildar essa imagem, você terá uma imagem com a aplicação pronta. O teste será executado dentro dela chamando o script npm test.
Script para rodar testes localmente
Um script de conveniência para rodar testes localmente usando Docker. Útil para reproduzir o passo de teste do workflow.
# run-tests.sh
IMAGE_NAME=myapp:local-test
# build a imagem localmente
docker build -t "$IMAGE_NAME" .
# roda os testes dentro do container e retorna o exit code dos testes
# assumimos que package.json define "test" para rodar a suíte
docker run --rm "$IMAGE_NAME" sh -c "npm test"
Executando esse script localmente você simula exatamente a etapa de teste do CI. Se os testes falharem, o container retorna código diferente de zero.
Exemplo prático completo
Abaixo está um workflow GitHub Actions completo que demonstra o fluxo: build para teste (load), executar testes no container carregado, e então publicar a imagem no GHCR. O pipeline usa duas execuções do buildx: uma com load para permitir docker run, outra para push.
Contexto: repositório com Dockerfile acima e package.json contendo scripts "test" e "start". O exemplo assume que o repositório pertence ao usuário/organization OWNER e que queremos publicar em ghcr.io/OWNER/myapp:tag.
# .github/workflows/ci-build-test-push.yml
name: CI Build Test Push to GHCR
on:
push:
tags:
- 'v*' # build/push somente em tags semânticas
pull_request:
branches:
- main
permissions:
contents: read
packages: write
jobs:
build-and-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup QEMU
uses: docker/setup-qemu-action@v2
- name: Setup buildx
uses: docker/setup-buildx-action@v2
- name: Login to GHCR
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build image (load for tests)
uses: docker/build-push-action@v4
with:
context: .
file: ./Dockerfile
push: false
load: true
tags: ghcr.io/${{ github.repository_owner }}/myapp:${{ github.ref_name }}
- name: Run tests inside built image
run: |
IMAGE=ghcr.io/${{ github.repository_owner }}/myapp:${{ github.ref_name }}
echo "Running tests in $IMAGE"
docker run --rm "$IMAGE" sh -lc "npm test"
- name: Rebuild and push to GHCR
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
uses: docker/build-push-action@v4
with:
context: .
file: ./Dockerfile
push: true
tags: |
ghcr.io/${{ github.repository_owner }}/myapp:${{ github.ref_name }}
ghcr.io/${{ github.repository_owner }}/myapp:latest
O que acontece em cada passo:
- Checkout do código.
- Configuração do buildx e qemu para suportar builds multiplataforma se necessário.
- Login no GHCR usando o GITHUB_TOKEN provido pelo Actions runner.
- Build da imagem com load: true — isso injeta a imagem no daemon Docker local do runner para que possamos usar docker run para executar testes.
- Executa os testes: docker run --rm IMAGE npm test. Se o comando retornar não-zero, o job falha e a imagem não é publicada.
- Se o evento for um push de tag (ex: v1.0.0), o job faz um segundo build com push: true para enviar a imagem ao GHCR com tags adequadas.
Resultado: somente imagens que passaram nos testes são publicadas. A separação entre build/test e push evita publicar imagens inválidas.
Variações e melhorias
Você pode otimizar para evitar rebuilds completos usando buildkit cache or build-push-action cache-from/push. Também é possível usar manifest lists para publicar multiplataforma com buildx push direto.
Se seus testes precisarem de banco de dados ou serviços, considere usar service containers no job ou rodar contêineres auxiliares com docker run antes dos testes.
Erros comuns, armadilhas e dicas práticas
Essa seção lista problemas típicos que iniciantes encontram e soluções diretas.
1) Testes passam localmente, falham no CI
Razões comuns: diferenças de ambiente, variáveis de ambiente não setadas no CI, dependências instaladas globalmente localmente. Solução: reproduza o ambiente com docker run local (construído com o mesmo Dockerfile) e rode os testes exatamente como o CI faz.
2) Problemas de autenticação no GHCR
Se o push falhar por permissão, verifique:
- Se o repositório pertence a uma organização, confirme que o GITHUB_TOKEN tem permissões para pacotes.
- Se usar PAT, deve ter escopo write:packages e delete:packages quando apropriado.
Também confirme o nome do registry: ghcr.io/OWNER/nome. Um erro comum é usar o nome do repositório sem o owner.
3) Tempo de execução e limitações dos runners
Builds Docker podem consumir tempo e espaço. Para builds pesados com dependências grandes, use cache e aproveite secrets para armazenar credentials do cache de registry privado. Se precisar de mais controle, considere runners self-hosted com recursos maiores.
Tip: ao trabalhar com imagens grandes, habilite build cache e minimize camadas no Dockerfile (combine RUNs) para reduzir tempo de build.
Próximos passos para aprofundar
Depois de dominar o fluxo básico, aprofunde em: multi-arch builds com buildx, signing de imagens (cosign), políticas de scan de vulnerabilidades (trivy/graal), e integração com orquestradores (Kubernetes, ArgoCD).
Considere também implementar stages de segurança: scan static (SAST), dependency scanning (Dependabot/OWASP), e runtime scanning antes do deploy. Automatize alerts e bloqueios quando vulnerabilidades críticas forem detectadas.
Automatizar rollbacks
Integre deploy com canary releases e monitoração: se métricas caírem, automatize rollback para tag anterior. Ferramentas como Flux/ArgoCD e sistemas de observabilidade ajudam a fechar esse ciclo.
Observabilidade
Adicione emissões de logs estruturados e métricas para a imagem que você publica. Assim, quando o deploy usar a imagem do GHCR, será possível correlacionar problemas em produção com commits e imagens específicas.
Conclusão prática
Pipeline: build (load para testar) -> executar testes no container -> push apenas quando testes passarem. Essa abordagem protege o registro de imagens quebradas e garante que a imagem publicada é a mesmo artefato testado.
Principais reforços:
- Testar no container reduz discrepâncias entre CI e produção.
- Use GITHUB_TOKEN para autenticação simples com GHCR quando possível.
- Separe build/test e push para evitar publicar imagens não testadas.
Pratique: pegue um repositório com testes reais, adicione o Dockerfile e o workflow apresentado, crie uma tag e verifique o fluxo completo. Ajuste timeouts, cache e serviços conforme sua stack.
Com esses passos você terá um CI simples, seguro e reproduzível, pronto para evoluir para pipelines mais complexas e integrações com deploy automatizado.
Assuntos: github-actions, Docker, ghcr, ci-cd, DevOps
Leia também
GitHub Actions para iniciantes: build Docker, push e deploy via SSH
Guia prático para iniciantes: configure GitHub Actions para build de imagem Docker, push ao registry e deploy automático via SSH. Inclui Dockerfile, workflow…
GitHub Actions OIDC: deploy sem segredos em AWS, GCP e Azure (setup prático)
Guia prático para configurar GitHub Actions OIDC e eliminar secrets nas nuvens: passo a passo para AWS, GCP e Azure, exemplos Terraform/gcloud/workflows e…
GitHub Actions + OIDC: provisionar infraestrutura com Terraform sem credenciais
Aprenda a usar OIDC do GitHub Actions para provisionar infraestrutura com Terraform na AWS sem armazenar credenciais. Guia prático com Terraform, IAM…
Escanear imagens Docker no CI com Trivy e GitHub Actions — falhe por CVSS
Aprenda a escanear imagens Docker no CI com Trivy e GitHub Actions e a fazer o pipeline falhar quando uma vulnerabilidade atingir um limiar CVSS. Passo a…
Testcontainers e GitHub Actions: testar imagens Docker na CI corretamente
Guia prático de como rodar testes de integração com imagens Docker usando Testcontainers no GitHub Actions: padrões certos e anti-patterns, workflows…
Docker Buildx + GitHub Actions: publicar imagens multi-arch (ARM/x86) corretamente
Guia prático para publicar imagens Docker multi-arch (arm64/x86) usando Docker Buildx e GitHub Actions. Mostro o jeito errado, o certo, workflows, debug e…