Projeto de IA sem automação vira um emaranhado de “funciona na minha máquina”. Um pipeline de CI/CD com GitHub Actions garante que cada mudança passe por lint, testes e build antes de chegar em produção — e que o deploy seja um clique (ou nenhum). Neste guia mostro como montar isso do zero para um projeto Python de IA.
Vou usar um caso comum: uma API FastAPI que carrega um modelo e responde inferências, empacotada em Docker.
O que é CI/CD e por que importa em IA
- CI (integração contínua): cada push roda lint, testes e validações automaticamente.
- CD (entrega contínua): o que passa nos testes é empacotado e publicado sem trabalho manual.
Em IA, isso é ainda mais valioso: dependências pesadas, código sensível a versão e modelos que precisam ser validados antes de ir ao ar.
Estrutura mínima do projeto
projeto/
app/main.py
tests/test_api.py
requirements.txt
pyproject.toml
Dockerfile
.github/workflows/ci.yml
O primeiro workflow: lint e testes
Comece simples. Este workflow roda em cada push e pull request:
name: CI
on:
push:
branches: [main]
pull_request:
jobs:
testes:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
cache: pip
- run: pip install -r requirements.txt
- run: pip install ruff pytest
- run: ruff check .
- run: pytest -q
O cache do pip acelera muito a instalação. Sem ele, cada execução reinstala tudo.
Matriz de versões do Python
Se você publica uma biblioteca, teste em várias versões de uma vez:
strategy:
matrix:
python-version: ["3.10", "3.11", "3.12"]
steps:
- uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
Testando sem gastar tokens de IA
Nunca chame a API real do modelo nos testes. Use mocks. Isso deixa a suíte rápida, gratuita e determinística.
from unittest.mock import patch
def test_resumir(client):
with patch("app.main.chamar_llm", return_value=("resumo", 10)):
r = client.post("/api/resumir", json={"texto": "conteudo"})
assert r.status_code == 200
assert r.json()["resumo"] == "resumo"
Build da imagem Docker no pipeline
Depois dos testes, construa a imagem. Use o cache de camadas do GitHub Actions para acelerar builds.
build:
needs: testes
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: docker/setup-buildx-action@v3
- uses: docker/build-push-action@v6
with:
context: .
push: false
tags: minha-api-ia:latest
cache-from: type=gha
cache-to: type=gha,mode=max
Publicando a imagem no registry
Para entregar de verdade, publique no GitHub Container Registry (GHCR):
- uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- uses: docker/build-push-action@v6
with:
context: .
push: true
tags: ghcr.io/${{ github.repository }}:${{ github.sha }}
Usar o SHA do commit como tag torna o deploy rastreável: você sempre sabe qual código está no ar.
Segredos e ambientes
Chaves de API ficam em Secrets do repositório, nunca no YAML.
- run: ./deploy.sh
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
Para deploy com aprovação manual, use Environments: você define revisores e o job só roda depois do aval.
Deploy automatizado
O deploy varia conforme o alvo. Um exemplo via SSH para um servidor:
deploy:
needs: build
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
environment: production
steps:
- name: Deploy via SSH
uses: appleboy/ssh-action@v1
with:
host: ${{ secrets.HOST }}
username: ${{ secrets.USER }}
key: ${{ secrets.SSH_KEY }}
script: |
docker pull ghcr.io/${{ github.repository }}:${{ github.sha }}
docker compose up -d --no-deps api
Qualidade de código com pre-commit
Rode o lint antes mesmo de chegar ao CI. O pre-commit evita commits problemáticos.
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.6.0
hooks:
- id: ruff
Erros comuns
- Chamar a API do modelo nos testes (lento, caro, instável).
- Colocar segredos direto no workflow.
- Não fixar versões de dependências — builds quebram sozinhos.
- Deploy sem aprovação em produção.
- Ignorar cache — pipelines lentos desmotivam o time.
Checklist de pipeline maduro
- Lint + formatação automáticos.
- Testes rápidos com mocks.
- Build de imagem com cache.
- Publicação com tag por commit.
- Deploy com ambiente protegido.
- Segredos fora do código.
Rodando o pipeline localmente antes de subir
Antes de confiar no CI, rode os mesmos passos na sua máquina. O act permite executar workflows do GitHub Actions localmente, o que acelera muito o ciclo de ajuste.
act -j testes
Se o pipeline passa local, é muito provável que passe no GitHub — e você evita o ciclo lento de commit, espera, erro e commit.
Estratégia de branches e proteção
O CI só protege de verdade se o merge for bloqueado quando os testes falham. Ative a proteção de branch na main: exija que o workflow passe antes de permitir o merge, e exija revisão de código. Isso transforma o pipeline em um portão real, não em um aviso ignorado.
Versionamento e releases automáticas
Quando você publica uma biblioteca, pode gerar tags e changelog automaticamente a partir dos commits. Ferramentas como o semantic-release analisam as mensagens no padrão Conventional Commits e decidem a próxima versão.
# commits no padrao
git commit -m "feat: adiciona endpoint de resumo"
git commit -m "fix: corrige timeout do provedor"
Com isso, a versão sobe sozinha e o changelog é escrito sem esforço manual.
Cache inteligente de dependências
O cache do pip é o que mais acelera o CI de Python. Além dele, cacheie também os navegadores do Playwright, se usar, e os modelos baixados em testes. Um pipeline que antes levava 8 minutos pode cair para 2.
- uses: actions/cache@v4
with:
path: ~/.cache/pip
key: pip-${{ hashFiles('requirements.txt') }}
Monitorando o custo do CI
Pipelines longos consomem minutos do plano. Fique de olho: rodar a matriz completa em todo push pode ser desperdício. Uma boa prática é rodar o conjunto completo só em pull requests e na main, deixando pushes de feature com uma suíte reduzida.
Testes de performance no pipeline
Além de corretude, meça performance. Um teste simples de latência evita que uma mudança deixe a API lenta sem ninguém perceber.
import time
def test_latencia_aceitavel():
inicio = time.perf_counter()
processar("texto de teste")
duracao = time.perf_counter() - inicio
assert duracao < 0.5 # segundos, com mock
Para benchmarks mais sérios, use pytest-benchmark e acompanhe a evolução ao longo do tempo.
Matriz de compatibilidade de dependências
Projetos de IA quebram quando uma dependência sobe de versão sozinha. Um job semanal que reinstala tudo do zero detecta isso antes de virar problema.
on:
schedule:
- cron: "0 6 * * 1" # toda segunda
jobs:
compatibilidade:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with: { python-version: "3.12" }
- run: pip install -U -r requirements.txt
- run: pytest -q
Se esse job falhar, você sabe que algo upstream mudou — antes que o cliente reclame.
Documentando o pipeline
Um badge de status no README mostra de imediato se o build está verde. Isso cria confiança e ajuda a detectar regressões visualmente.

Deploy blue-green com GitHub Actions
Para reduzir risco, mantenha duas versões no servidor e troque o tráfego só depois que a nova passar no healthcheck. Se algo falhar, o rollback é imediato.
script: |
docker pull ghcr.io/${{ github.repository }}:${{ github.sha }}
docker tag ghcr.io/${{ github.repository }}:${{ github.sha }} app:novo
docker compose up -d --no-deps --scale api=2 api
sleep 10
curl -f http://localhost:8000/health || (docker compose up -d --no-deps api && exit 1)
Notificações de falha
Pipeline que falha em silêncio é pipeline inútil. Notifique o time quando algo quebrar — no Slack, no Telegram ou por e-mail.
- name: Avisar no Telegram
if: failure()
run: |
curl -s -X POST "https://api.telegram.org/bot${{ secrets.TG_TOKEN }}/sendMessage" \
-d chat_id=${{ secrets.TG_CHAT }} \
-d text="CI falhou: ${{ github.repository }} (${{ github.sha }})"
Dependabot e atualizações automáticas
Manter dependências atualizadas reduz vulnerabilidades. O Dependabot abre pull requests automáticos quando surge uma versão nova, e o seu pipeline valida cada uma antes do merge.
version: 2
updates:
- package-ecosystem: pip
directory: "/"
schedule:
interval: weekly
- package-ecosystem: github-actions
directory: "/"
schedule:
interval: monthly
Conclusão e próximos passos
Um pipeline maduro evolui por etapas: primeiro lint e testes, depois build e publicação, por último deploy com aprovação. Não tente montar tudo de uma vez — adicione uma camada por vez e valide o ganho.
Segredos: OIDC em vez de chaves de longa duração
Guardar chaves de nuvem como segredos é arriscado. Uma alternativa moderna é o OIDC: o GitHub Actions assume uma identidade temporária na nuvem, sem chave estática. Isso elimina um vetor de vazamento.
permissions:
id-token: write
contents: read
steps:
- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123:role/deploy
aws-region: sa-east-1
Limites de tempo e concorrência
Sem controle, um workflow pode ficar preso por horas e consumir minutos do plano. Defina timeout-minutes e agrupe execuções por branch para cancelar as antigas.
concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true
jobs:
testes:
timeout-minutes: 15
Resumo
CI/CD não é luxo de empresa grande. Com algumas dezenas de linhas de YAML você garante que toda mudança passe por lint, testes e build antes de ir ao ar — e que o deploy seja reproduzível. Comece pelo workflow de testes, adicione build e só depois automatize o deploy. O ganho de confiança é imediato.
