Escanear imagens Docker no CI com Trivy e GitHub Actions — falhe por CVSS
Por John Pires · 20 de março de 2026

Por que aprender isso em 2026
O desenvolvimento moderno empacota aplicações em imagens Docker e as entrega continuamente. Em 2026, ataques automatizados exploram vulnerabilidades conhecidas com velocidade; integrar uma verificação automática no CI é defesa básica. Não basta “rodar um scanner” — é preciso automatizar a decisão: recusar builds quando uma vulnerabilidade atinge um nível de risco numérico (CVSS).
Este artigo mostra passo a passo como usar Trivy para escanear imagens Docker dentro do GitHub Actions e como fazer o pipeline falhar automaticamente se qualquer vulnerabilidade tiver CVSS maior ou igual a um limiar (ex: 7.0).
Você vai ver blocos de código reais: um Dockerfile para uma aplicação simples, um workflow do GitHub Actions que constrói, escaneia e falha por CVSS, e um script em Python que interpreta o JSON do Trivy — porque Trivy nem sempre expõe diretamente um "fail-by-cvss" numa flag única.
Por que CVSS e não só severidade
Trivy tem flags de severidade (LOW, MEDIUM, HIGH, CRITICAL) que são úteis. Mas o CVSS fornece uma pontuação numérica (0.0–10.0). Para políticas corporativas, muitas equipes preferem um limite numérico porque dá controle fino: por exemplo, falhar apenas quando CVSS >= 7.0 elimina falsos positivos de severidade média que têm CVSS baixo.
Resultados esperados
Ao final você terá: (1) um workflow que constrói a imagem Docker, (2) Trivy gerando relatório JSON, (3) um verificador que sai com código 1 se existir vulnerabilidade com CVSS >= limiar. O job do GitHub Actions falhará automaticamente quando a regra for satisfeita.
Configuração do ambiente
Antes de começar: instale Docker localmente e Trivy para testes locais. No CI (GitHub Actions) não é necessário instalar localmente a ferramenta se você usar a action oficial ou instalar via script. Aqui mostramos ambas abordagens — usar a action simplifica, mas rodar o binário e gerar JSON dá mais controle.
Instalando Trivy localmente
Para testar localmente recomendo usar a release oficial. No Linux/WSL:
Comando para instalar (contexto: instala o binário /usr/local/bin/trivy):
curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sudo sh -s -- -b /usr/local/bin v0.44.0
O comando acima baixa e instala uma versão específica. Depois rode trivy --version para validar. Este passo é opcional se você só vai usar GitHub Actions.
Depois, teste um scan local com uma imagem oficial:
trivy image --format json --output report.json alpine:latest
O comando gera um report.json com estrutura que vamos parsear no verificador Python.
Permissões e tokens no GitHub
No repositório crie um secret (Settings -> Secrets -> Actions) chamado GHCR_PAT se você publicar imagens em GitHub Container Registry, ou mantenha nada se só construir e escanear localmente no runner. Para puxar imagens privadas, configure autenticação adicional.
Se usar a action do Trivy você não precisa de token extra para rodar o scanner; para puxar imagens privadas configure o docker login no workflow antes do scan.
Hello World progressivo
Vamos construir uma imagem Docker mínima que serve uma página estática com nginx. Esse é o "hello world" real para scans: contém uma base (alpine/nginx) com pacotes que Trivy pode analisar.
Dockerfile explicado
Crie um Dockerfile simples que copia um index.html para um nginx leve:
FROM nginx:alpine
COPY index.html /usr/share/nginx/html/index.html
O que o Dockerfile faz: usa a imagem base nginx:alpine e substitui a página padrão. Depois de construir, a imagem pode ser escaneada pelo Trivy. Base em alpine é intencional: muitos scanners encontram vulnerabilidades em bibliotecas C; você verá resultados reais.
Construir e escanear localmente
Etapas para testar localmente:
- Salvar o Dockerfile e um index.html no mesmo diretório.
- Construir a imagem:
docker build -t meu-nginx-hello:local . - Rodar Trivy e gerar JSON:
trivy image --format json --output report.json meu-nginx-hello:local
Depois verifique o report.json. A estrutura contém um array de resultados por target (pacote, tipo, vulnerabilidades). Vamos automatizar a interpretação desse JSON no CI.
Mini-projeto prático
Objetivo: configurar um GitHub Actions workflow que:
- Constrói a imagem
- Roda Trivy gerando JSON
- Executa um verificador que falha se existir CVSS >= LIMIAR (ex: 7.0)
Vamos direto ao arquivo do workflow. Coloque em .github/workflows/ci-trivy-cvss.yml.
Workflow do GitHub Actions
Arquivo completo do workflow (explicação antes e depois): este workflow usa ubuntu-latest, constrói a imagem, instala Trivy via script, roda o scan em JSON e chama o script Python para checar CVSS. Se o script retornar código 1 o job falha.
name: CI - Build & Trivy CVSS Check
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
build-and-scan:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Build image
run: |
docker build -t ghcr.io/${{ github.repository_owner }}/meu-nginx-hello:${{ github.sha }} .
- name: Install trivy
run: |
curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sudo sh -s -- -b /usr/local/bin v0.44.0
- name: Trivy scan (json)
run: |
trivy image --quiet --format json --output trivy-report.json ghcr.io/${{ github.repository_owner }}/meu-nginx-hello:${{ github.sha }}
- name: Check CVSS threshold
run: |
python3 .github/scripts/check_cvss.py trivy-report.json 7.0
O que acontece: o job constrói uma imagem e a marca com o SHA do commit. Trivy é instalado e gera trivy-report.json. O script Python interpreta esse JSON e sai com código 1 se algum CVSS >= 7.0.
Script Python: check_cvss.py
Coloque este script em .github/scripts/check_cvss.py. Ele lê o JSON do Trivy e avalia CVSS v3 (quando disponível) ou CVSS v2. O script é intencionalmente simples e robusto para formato real do Trivy.
#!/usr/bin/env python3
import sys
import json
if len(sys.argv) != 3:
print("Usage: check_cvss.py <trivy-json> <threshold>")
sys.exit(2)
report_file = sys.argv[1]
threshold = float(sys.argv[2])
with open(report_file, 'r') as f:
data = json.load(f)
violations = []
# Trivy returns array 'Results'
for result in data.get('Results', []):
for vuln in result.get('Vulnerabilities', []) or []:
# Try CVSS v3 first, then v2, then skip
cvssv3 = vuln.get('CVSS', {}).get('nvd', {}).get('V3Vector', None)
# Numeric scores may be in 'CVSS' -> 'nvd' -> 'V3Score' or 'V2Score'
numeric_v3 = vuln.get('CVSS', {}).get('nvd', {}).get('V3Score')
numeric_v2 = vuln.get('CVSS', {}).get('nvd', {}).get('V2Score')
score = None
if isinstance(numeric_v3, (int, float)):
score = float(numeric_v3)
elif isinstance(numeric_v2, (int, float)):
score = float(numeric_v2)
if score is not None and score >= threshold:
violations.append({
'pkg': vuln.get('PkgName'),
'vuln_id': vuln.get('VulnerabilityID'),
'score': score,
'title': vuln.get('Title')
})
if violations:
print(f"Found {len(violations)} vulnerabilities with CVSS >= {threshold}:")
for v in violations:
print(f"- {v['vuln_id']} {v['pkg']} score={v['score']} title={v['title']}")
sys.exit(1)
else:
print(f"No vulnerabilities with CVSS >= {threshold} detected.")
sys.exit(0)
O que o script faz: percorre o JSON do Trivy, tenta extrair a pontuação CVSS v3/v2 e compara com o limiar. Se encontrar alguma >= threshold, imprime detalhes e retorna 1 — o GitHub Actions registra falha.
Executando localmente o mesmo fluxo
Você pode simular localmente: construa a imagem, rode Trivy gerando JSON e execute o script:
docker build -t meu-nginx-hello:local .
trivy image --format json --output trivy-report.json meu-nginx-hello:local
python3 .github/scripts/check_cvss.py trivy-report.json 7.0
Resultado: o script imprime se encontrou ou não vulnerabilidades com CVSS alto. Use isso para ajustar o limiar antes de colocar em produção.
Erros comuns, armadilhas e dicas práticas
Esta seção mostra problemas reais que você vai encontrar e como resolver rapidamente. Pratique corrigir o pipeline em commits de PR para não bloquear o time sem aviso.
Falso positivo por pacotes sem CVE
Às vezes Trivy relata vulnerabilidades com dados de NVD incompletos (sem V3Score). O script acima tenta V3Score e V2Score. Se sua política exigir só V3, ajuste o script para ignorar V2 ou tratar como menor risco.
Workflow lento por download de base
Instalar Trivy a cada execução aumenta tempo. Use uma cache de runner ou uma action que já tenha Trivy. Outra saída: usar image-scanning actions de marketplace que fazem cache para você, mas perca controle sobre o JSON detalhado.
Erros de autenticação ao escanear imagens privadas
Se seu workflow precisa puxar imagens privadas (por exemplo, base image privada), configure docker/login-action antes do scan e exporte credenciais. Sem isso Trivy pode falhar ao tentar baixar layers para analisar.
Dica: coloque o verificador de CVSS como etapa final e envie o relatório JSON como artifact do job para análise posterior com actions/upload-artifact.
Recursos para continuar estudando
Depois de ter o pipeline básico funcionando, você vai querer: automatizar correções (dependabot/container-image updates), integrar SCA para dependências de linguagem e configurar alertas de segurança quando subir para produção.
Documentação e leituras recomendadas
- Aqua Security Trivy: documentação oficial e flags avançadas
- GitHub Actions: conceitos de jobs, secrets e artefatos
- CVSS documentation: entenda o que cada componente da pontuação significa
Próximos passos práticos
Implemente um mecanismo de aprovação em PRs: se o pipeline falhar por CVSS, crie um comentário automático com instruções de mitigação. Integre com tickets (Jira/GitHub Issues) para rastrear correções.
Considere também usar SBOM (Software Bill of Materials) e scanners para dependências de linguagem (ex: npm audit, pip-audit) em paralelo com o Trivy, que faz SCA e imagem.
Conclusão prática
Automatizar scans de imagens Docker no CI com Trivy e GitHub Actions é uma proteção fundamental. Este artigo forneceu um caminho prático: Dockerfile, workflow GitHub Actions, script de verificação de CVSS e dicas para evitar armadilhas comuns.
Resumo do fluxo: construa a imagem, gere relatório JSON com Trivy, rode um verificador que interpreta CVSS e retorne código 1 quando o limiar for atingido. Isso transforma um scanner passivo em uma regra ativa de gate de qualidade.
Implemente o exemplo no seu repositório, ajuste o limiar para o nível de risco da sua equipe, registre os relatórios como artifacts e configure alertas — assim você transforma segurança em algo previsível, automatizado e escalável.
Se precisar, adapte o script para aceitar múltiplos limiares por severidade ou por ambiente (staging vs production). O importante é começar simples e evoluir com dados reais dos relatórios.
Boa prática: depois de liberar o pipeline, faça PRs experimentais com imagens intencionalmente vulneráveis (ambiente controlado) para verificar se o workflow realmente falha como esperado.
Assuntos: Docker, ci-cd, github-actions, trivy, DevOps
Leia também
GitHub Actions: build, testar em container e publicar imagem no GHCR
Pipeline prática: build de imagem Docker, executar testes no container carregado e publicar no GHCR usando GitHub Actions. Código e workflow para iniciar e…
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…
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…
Testes em CI com GitHub Actions e Docker Compose para Postgres e Redis
Aprenda a rodar testes de integração no CI usando GitHub Actions e Docker Compose com Postgres e Redis. Guia passo a passo para iniciantes: arquivos…