GitHub Actions para iniciantes: build Docker, push e deploy via SSH
Por John Pires · 08 de março de 2026

Introducao ao problema
O que vamos automatizar
Você tem uma aplicação (por exemplo, uma API Node.js ou um serviço em Go) que precisa ser empacotada em uma imagem Docker, enviada para um registry e atualizada automaticamente em um servidor de produção via SSH. Fazer isso manualmente é sujeito a esquecimento, erros de versão e downtime desnecessário. O objetivo é implementar uma pipeline simples e robusta usando GitHub Actions para automatizar build, push e deploy via SSH.
Este fluxo cobre três operações críticas: build da imagem, push para um registry (Docker Hub ou GitHub Container Registry) e deploy remoto executando comandos via SSH no servidor alvo. Cada etapa deve ser reproduzível e segura (uso de secrets), além de fornecer logs para depuração.
Vou explicar conceitos do zero com analogias diretas: pense no processo como uma linha de montagem industrial — o código fonte entra, as máquinas constroem o produto (Docker build), o caminhão leva o produto ao depósito (push para registry) e um gerente remoto instrui o depósito a colocar o novo produto na prateleira (SSH executa docker pull + restart).
Por que usar Actions + Docker + SSH
GitHub Actions fornece integração estreita com o repositório — cada push ou merge pode disparar a pipeline automaticamente. Docker garante que o ambiente de execução seja consistente. SSH ainda é o método mais direto para controlar servidores que você já administra (sem necessidade de serviços de deploy terceirizados).
Vantagens práticas: reprodutibilidade, versionamento da imagem (tags), rollback controlado (mantendo imagens anteriores no registry) e logs centralizados do GitHub Actions. Desvantagens a ter em mente: é preciso proteger chaves SSH e tokens do registry, além de configurar o servidor para permitir atualizações via Docker.
Regra prática: não copie chaves nem tokens no repositório. Sempre use GitHub Secrets e restringa permissões no servidor.
Pre-requisitos e setup
Ambiente local
Pré-requisitos no seu PC de desenvolvimento:
- Git configurado e repositório no GitHub
- Conta em um registry (Docker Hub ou GitHub Container Registry)
- Docker instalado para testar localmente
Você precisa validar localmente o Dockerfile e garantir que a aplicação roda em container antes de configurar a pipeline. Isso reduz ciclos de tentativa/erro no CI.
Servidor remoto e chaves SSH
No servidor de destino você deve ter Docker instalado e um usuário com permissão para executar containers (ou docker-compose). Vamos usar SSH para conectar e executar os comandos de atualização.
Gere uma chave SSH sem passphrase e adicione a public key ao servidor (authorized_keys). Exemplo de geração e cópia:
O código abaixo cria a chave e copia a chave pública para o servidor remoto. Antes de rodar, substitua user@host pela sua conexão.
# Gera uma chave SSH sem passphrase (execute localmente)
ssh-keygen -t ed25519 -C "github-actions-deploy" -f ~/.ssh/id_ed25519_github_actions -N ''
# Copia a chave pública para o servidor remoto (substitua user@host)
ssh-copy-id -i ~/.ssh/id_ed25519_github_actions.pub user@host
Depois de executar, você terá a chave privada que será adicionada como GitHub Secret (por exemplo, SSH_PRIVATE_KEY). Adicione também o user@host ao host known_hosts ou configure o workflow para aceitar a fingerprint de forma segura.
Crie os seguintes Secrets no repositório GitHub (Settings > Secrets):
- DOCKER_USERNAME e DOCKER_PASSWORD (ou GITHUB_TOKEN/CR_PAT para registries)
- SSH_PRIVATE_KEY (conteúdo do arquivo id_ed25519_github_actions)
- SERVER_USER e SERVER_HOST (opcionais, para não hardcodar)
Tutorial guiado passo a passo
Dockerfile e build
Exemplo real: API Node.js. Este Dockerfile produz uma imagem minimalista com multi-stage build para reduzir tamanho final.
# Dockerfile
# Stage de build
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --production=false
COPY . .
RUN npm run build
# Stage de runtime
FROM node:18-alpine
WORKDIR /app
COPY --from=builder /app/package*.json ./
RUN npm ci --production
COPY --from=builder /app/dist ./dist
EXPOSE 3000
CMD ["node", "dist/index.js"]
O que este Dockerfile faz: no primeiro estágio instala dependências de desenvolvimento e roda o build (por exemplo, transpilar TypeScript). No segundo estágio instala somente dependências de produção e copia o build final, resultando em uma imagem menor. Depois de executar docker build localmente, você terá uma imagem pronta para rodar em produção.
Teste local do build e run:
# build localmente com tag de teste
docker build -t myapp:local .
# rodar localmente
docker run -p 3000:3000 --rm myapp:local
Se o container inicia e responde, o Dockerfile está pronto para o CI.
GitHub Actions workflow
A seguir, um workflow completo que:
- Dispara em push na branch main
- Faz check-out do código
- Login no Docker Hub
- Builda e push da imagem
- Conecta via SSH e executa comandos de deploy (docker pull + restart)
Coloque o arquivo em .github/workflows/ci-cd.yml
# .github/workflows/ci-cd.yml
name: CI CD Build and Deploy
on:
push:
branches: [ main ]
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up QEMU (if needed for cross-build)
uses: docker/setup-qemu-action@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: ${{ secrets.DOCKER_USERNAME }}/myapp:${{ github.sha }}
- name: Deploy via SSH
uses: appleboy/ssh-action@v0.1.8
with:
host: ${{ secrets.SERVER_HOST }}
username: ${{ secrets.SERVER_USER }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
port: 22
script: |
docker pull ${{ secrets.DOCKER_USERNAME }}/myapp:${{ github.sha }}
docker stop myapp || true
docker rm myapp || true
docker run -d --name myapp -p 3000:3000 ${{ secrets.DOCKER_USERNAME }}/myapp:${{ github.sha }}
Explicação: o job usa o docker/build-push-action para construir e empurrar a imagem com tag igual ao SHA do commit. Em seguida, appleboy/ssh-action conecta ao servidor usando a chave privada (guardada em secrets) e executa comandos para atualizar o container.
O que acontece após o push: a imagem com tag específica é armazenada no registry. O SSH se conecta e força o servidor a baixar a nova imagem e executar o container. O uso de tags imutáveis (sha) evita problemas de cache e garante rollback fácil — basta redeploy de outra tag existente.
Servidor: docker-compose alternativa
Se no servidor você preferir docker-compose, crie um docker-compose.yml com imagem parametrizada e use docker-compose pull + up -d no script SSH. Exemplo:
# docker-compose.yml (exemplo server side)
version: '3.8'
services:
web:
image: yourdockeruser/myapp:latest
ports:
- "3000:3000"
restart: always
Depois altere o script de deploy no workflow para:
docker-compose pull web
docker-compose up -d --no-deps web
Com docker-compose você centraliza configurações como variáveis de ambiente e volumes no server.
Erros comuns e armadilhas
A seguir problemas que aparecerão no caminho e como evitá-los.
Chaves SSH mal configuradas: se o host não aceitar a key, a ação falhará com erro de autenticação. Verifique authorized_keys, permissões (chmod 600 na private key local), e se o usuário tem permissão para executar Docker.
Secrets incorretos: variáveis com nomes errados levam a falhas de login no registry. Sempre teste as credenciais localmente (docker login).
Imagens sem tag imutável: usar 'latest' sem versão impede rollback e causa problemas de cache. Prefira tags por SHA ou semver.
Permissões do Docker no servidor: se o usuário remoto não estiver no grupo docker, será necessário sudo. Ajuste o script para usar sudo docker ... ou configure permissões adequadas.
- Problema: tempo limite de conexão SSH — cheque firewall e porta.
- Problema: ação appleboy/ssh-action não encontra known_hosts — valide fingerprint no script ou pré-popule.
Dica rápida:
Teste cada camada localmente: primeiro Docker build/run, depois docker pull em um servidor de teste, finalmente conecte via SSH e execute os comandos manualmente antes de automatizar.
Resultado final funcionando
Como verificar
Checklist para confirmar que o fluxo está operacional:
- Ao dar push na branch main: workflow inicia no GitHub Actions
- Job de build conclui sem erros e image é enviada ao registry
- Etapa SSH retorna sucesso e os comandos de deploy são executados
- O serviço no servidor responde na porta esperada
Valide também logs do container no servidor:
# comandos no servidor para checar
docker logs -f myapp
# ou, se usar docker-compose
docker-compose logs -f web
Se os logs mostrarem inicialização correta, o deploy foi bem-sucedido. Caso contrário, os logs ajudarão a identificar erros de runtime (variáveis de ambiente, portas, dependências externas).
Logs e troubleshooting
GitHub Actions fornece logs detalhados por etapa. Se a etapa de deploy via SSH falhar, verifique o output da action appleboy (ela imprime stdout/stderr do remoto). Para problemas de build/push, verifique se as credenciais do registry estão válidas e se o nome da imagem está correto.
Passos práticos para diagnosticar:
- Rodar docker build e push localmente usando as mesmas credenciais.
- SSH manual para o servidor e executar docker pull manual.
- Verificar permissões e versões do Docker no servidor.
Desafio para o leitor praticar
Tarefas obrigatórias
Implemente o pipeline no seu repositório e conclua as seguintes tarefas:
- Automatizar build e push para uma imagem taggeada com SHA
- Configurar deploy via SSH que reinicie o serviço com a nova imagem
- Testar rollback: execute o deploy de uma tag anterior e valide que o serviço volta ao estado anterior
Documente no README do projeto os secrets que precisa configurar e os comandos manuais que você usou para validar cada passo.
Extra: monitoramento e rollback
Para avançar, adicione monitoramento simples que verifique health endpoint após deploy. Exemplo de script no servidor que faz um curl para /health e tenta rollback em caso de falha:
# exemplo de script de deploy com healthcheck (server-side)
#!/bin/bash
IMAGE=$1
docker pull $IMAGE
docker stop myapp || true
docker rm myapp || true
docker run -d --name myapp -p 3000:3000 $IMAGE
sleep 5
if ! curl -fsS http://localhost:3000/health; then
echo "Healthcheck failed, rolling back"
# aqui você poderia puxar a tag anterior salva em um arquivo
docker pull yourdockeruser/myapp:previous
docker stop myapp || true
docker rm myapp || true
docker run -d --name myapp -p 3000:3000 yourdockeruser/myapp:previous
fi
Guardar a tag anterior em um pequeno arquivo no servidor (por exemplo, LAST_DEPLOY_TAG) facilita o rollback manual ou automatizado.
Conclusao pratica
Automatizar build, push e deploy com GitHub Actions e SSH é uma solução direta e poderosa para equipes pequenas que controlam seus próprios servidores. O fluxo apresentado é minimalista, seguro quando se usa secrets, e permite rollback e rastreabilidade por tags imutáveis.
Checklist final prático:
- Validar Dockerfile localmente
- Adicionar secrets no GitHub (SSH_PRIVATE_KEY, DOCKER_USERNAME, DOCKER_PASSWORD, SERVER_HOST, SERVER_USER)
- Implementar workflow conforme exemplo e testar com commits na branch main
- Monitorar logs e implementar healthchecks e políticas de rollback
Resumo técnico: prefira tags imutáveis (sha), mantenha chaves fora do repositório, teste cada camada localmente e use logs do Actions + logs do container para solução de problemas. Com esses passos é possível ter deploys previsíveis e menos tempo gasto em tarefas operacionais repetitivas.
Próxima ação recomendada: implemente o pipeline no seu repositório de testes e compartilhe o primeiro log de sucesso com o time — evidência prática vale mais que teoria.
Assuntos: DevOps, Docker, github-actions, ci-cd, ssh
Leia também
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…
Monitorar containers Docker com Prometheus e Grafana via Docker Compose — guia prático
Guia prático para monitorar containers Docker com Prometheus e Grafana via Docker Compose. Inclui arquivos reais, aplicação instrumentada, cAdvisor e dicas…
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…
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…
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…