Como configurar um pipeline de deploy automático com GitHub Actions
Deploy & DevOps · Por Kevin Marcondes · Publicado em · Atualizado em · 8 min de leitura
Este tutorial mostra, passo a passo, como configurar um pipeline de deploy automático com GitHub Actions. Você vai criar um workflow YAML que testa, faz build com Docker, publica a imagem no GHCR e realiza o deploy via SSH com Docker Compose. O exemplo é genérico e pode ser adaptado a diferentes stacks.
Pré-requisitos
Você precisa de um repositório no GitHub e permissão para criar workflows. No servidor de destino, tenha Docker e Docker Compose instalados, acesso por SSH e portas liberadas no firewall. Crie um endpoint de healthcheck na sua aplicação. Defina um domínio e aponte o DNS para o servidor, se for expor na web.
Se o seu projeto ainda não tem testes automatizados, comece simples. Se usa PHP legado, veja como iniciar testes gradualmente em código legado PHP.
- Repositório no GitHub com branch principal (ex.: main)
- Servidor Linux com Docker e Docker Compose
- Acesso SSH com chave privada
- Endpoint /health ou similar
- Domínio configurado (opcional)
1) Criar o workflow básico
Vamos iniciar com um workflow mínimo que roda em push e pull request para a branch principal. Ele fará checkout do código e preparará o ambiente do pipeline. A partir dele, evoluiremos para testes, build e deploy.
# .github/workflows/deploy.yml
name: CI/CD
on:
push:
branches: ["main"]
pull_request:
branches: ["main"]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
2) Cachear dependências e rodar testes
Como exemplo, usaremos Node.js. Adapte para sua stack. O cache acelera instalações. Rode testes a cada push e PR para evitar regressões. Se ainda não houver testes, valide ao menos um lint ou build rápido.
Para projetos PHP/Laravel, há um guia dedicado de CI/CD em GitHub Actions para PHP/Laravel.
# Adicione ao job build
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Instalar deps
run: npm ci
- name: Testes
run: npm test --if-present
3) Build da imagem Docker e push para GHCR
Empacotar a aplicação em Docker padroniza o deploy. Vamos publicar no GitHub Container Registry (GHCR) usando o GITHUB_TOKEN. Isso evita criar registries externos.
Se você está decidindo entre containers e VMs para produção, compare prós e contras em Docker vs VMs.
- Tags imutáveis (SHA) facilitam rastreabilidade e rollback
- Defina permissions -> packages: write para usar GHCR
# No mesmo arquivo, após o job build
docker_image:
needs: build
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
env:
IMAGE_NAME: ghcr.io/${{ github.repository }}
outputs:
image_tag: ${{ steps.meta.outputs.tag }}
steps:
- uses: actions/checkout@v4
- name: Definir tag da imagem
id: meta
run: echo "tag=${GITHUB_SHA}" >> $GITHUB_OUTPUT
- name: Login no GHCR
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build e push
uses: docker/build-push-action@v5
with:
context: .
file: ./Dockerfile
push: true
tags: |
${{ env.IMAGE_NAME }}:${{ steps.meta.outputs.tag }}
${{ env.IMAGE_NAME }}:main
4) Preparar o servidor com Docker Compose
No servidor, usaremos Docker Compose para orquestrar a aplicação. O arquivo abaixo referencia a imagem do GHCR e usa variáveis REPO_IMAGE e IMAGE_TAG, que enviaremos no deploy.
Atenção: executar o Compose com a nova imagem reiniciará o serviço. Use janela de manutenção ou técnicas de blue/green para evitar indisponibilidade.
# docker-compose.yml (no servidor)
version: '3.8'
services:
app:
image: ghcr.io/${REPO_IMAGE}:${IMAGE_TAG}
env_file:
- .env
ports:
- "80:3000"
restart: always
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
interval: 10s
timeout: 3s
retries: 5
# Mantenha um arquivo .env com as variáveis de runtime da sua app.
5) Deploy via SSH usando Docker Compose
O job de deploy conecta por SSH, envia as variáveis de versão e aciona o Docker Compose no servidor. Ele também faz login no GHCR para puxar a imagem privada.
Guarde chaves e tokens como segredos no repositório. Nunca comite segredos no Git.
- Segredos necessários: SSH_HOST, SSH_USER, SSH_KEY, GHCR_USER, GHCR_TOKEN (read:packages)
deploy:
needs: docker_image
runs-on: ubuntu-latest
environment:
name: production
steps:
- uses: actions/checkout@v4
- name: Preparar chave SSH
run: |
mkdir -p ~/.ssh
echo "${{ secrets.SSH_KEY }}" > ~/.ssh/id_rsa
chmod 600 ~/.ssh/id_rsa
ssh-keyscan -H ${{ secrets.SSH_HOST }} >> ~/.ssh/known_hosts
- name: Enviar vars de versão
env:
IMAGE_TAG: ${{ needs.docker_image.outputs.image_tag }}
run: |
printf "REPO_IMAGE=%s\nIMAGE_TAG=%s\n" "${{ github.repository }}" "$IMAGE_TAG" > .env.deploy
scp -i ~/.ssh/id_rsa .env.deploy ${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST }}:/opt/app/.env
- name: Deploy remoto
env:
GHCR_USER: ${{ secrets.GHCR_USER }}
GHCR_TOKEN: ${{ secrets.GHCR_TOKEN }}
run: |
ssh -i ~/.ssh/id_rsa ${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST }} \
"echo $GHCR_TOKEN | docker login ghcr.io -u $GHCR_USER --password-stdin && \
cd /opt/app && docker compose pull && docker compose up -d && docker image prune -f"
6) Segredos, ambientes e aprovações
Use GitHub Environments para isolar segredos por ambiente e exigir aprovação humana antes do deploy em produção. Defina os segredos no ambiente correspondente e marque revisores obrigatórios. No YAML, apenas referencie o nome do ambiente. A aprovação acontece fora do YAML, na configuração do repositório.
- Crie environments: staging e production
- Guarde segredos por ambiente
- Exija aprovação para production
- Adapte notificação (Slack, e‑mail) se necessário
deploy:
needs: docker_image
runs-on: ubuntu-latest
environment:
name: production
url: https://sua-app.com
steps:
- uses: actions/checkout@v4
# ... (restante igual ao passo anterior)
7) Como confirmar que funcionou
Valide o healthcheck e verifique os contêineres em execução. Confirme a versão pelo tag/commit exposto na aplicação, se houver. Automatize uma checagem simples no final do deploy para falhar cedo em caso de erro.
- Healthcheck HTTP 200
- Container saudável no Docker
- Logs sem erros críticos
# Do runner ou da sua máquina
curl -fsS https://sua-app.com/health && echo "OK"
# No servidor remoto
ssh $USER@$HOST "docker ps --format 'table {{.Names}}\t{{.Image}}\t{{.Status}}' && docker compose ps && docker compose logs --tail=50"
8) Problemas comuns e soluções
Veja alguns erros frequentes e como resolver. Evite alterações manuais no servidor que não estejam descritas como código. Isso dificulta reprodutibilidade e rollback. Para migrações de banco, opte por práticas seguras para evitar indisponibilidade.
- Host key verification failed: execute ssh-keyscan para popular known_hosts.
- Permissão negada no GHCR: confirme GHCR_USER e GHCR_TOKEN com escopo read:packages.
- docker compose: comando ausente? Instale o plugin/CLI Compose no servidor.
- Timeout no healthcheck: confira portas e firewall; valide a rota /health.
- Rollback: rode docker compose up -d com a tag anterior (SHA anterior).
- Migrações sem downtime: veja versionamento de banco e zero‑downtime com CDC.
9) Próximos passos e melhorias
Você pode adicionar matrix de versões, análise estática e varredura de vulnerabilidades. Para pipelines maiores, audite seu processo e trate gargalos, filas longas e pontos únicos de falha. Quando o deploy ficar crítico para o negócio, invista em observabilidade e revisões periódicas do pipeline.
- Matrix de versões (Node, Python, PHP)
- Builds paralelos por serviço (monorepo)
- Varredura SCA/Container
- Blue/green ou canary deploy
- Observabilidade e alertas
- Auditoria de processos de deploy
Perguntas frequentes
Preciso usar Docker para fazer deploy com GitHub Actions?
Não. Você pode rsync de artefatos (build estático) ou publicar pacotes e instalar no servidor. Docker padroniza o runtime e facilita rollback, mas não é obrigatório. Adapte o job de deploy para seu processo atual (systemd, PM2, etc.).
Como faço rollback rápido se a versão deu problema?
Use tags imutáveis com SHA. No servidor, rode o Docker Compose fixando a tag anterior e aplique “docker compose up -d”. Mantenha um histórico curto de imagens locais para acelerar o retorno e monitore o healthcheck após a troca.
Como proteger segredos no pipeline?
Guarde chaves e tokens nos Secrets do repositório ou do Environment, jamais no código. Restrinja permissões, rotacione chaves, e use ambientes com aprovação para produção. Limite logs para não expor valores sensíveis durante o pipeline.
Posso ter múltiplos ambientes (staging e produção)?
Sim. Crie dois environments e dois jobs de deploy ou um único job parametrizado por input manual (workflow_dispatch). Armazene segredos por ambiente e libere o deploy de produção apenas após aprovação e verificação do staging.
E quanto a migrações de banco no deploy?
Planeje migrações com backward compatibility. Aplique primeiro mudanças que não quebram a versão atual. Execute migrações no pipeline com passos idempotentes, monitore o impacto e, quando necessário, adote estratégias de zero‑downtime com CDC.
Quer ir além do básico do pipeline?
Se o seu deploy exige estratégias como blue/green, canary, migrações sensíveis e observabilidade, evolua a arquitetura e o processo com revisões técnicas e automações sob medida.
Discutir melhorias de CI/CDVeja também
- tutorial: implementar Canary releases com Traefik em Docker Compose usando headers e weight routing para deploys graduais de aplicações PHP
- Tutorial: configurar HAProxy com Let's Encrypt e healthchecks para balanceamento de tráfego entre containers Docker em VPS (passo a passo)
- Tutorial prático: implementar mTLS entre Nginx e microserviços usando smallstep CA em Docker Compose
- Tutorial: configurar cgroup v2 e systemd ResourceControl para limitar CPU, memória e I/O de containers Docker no Debian