Cómo configurar un pipeline de despliegue automático con GitHub Actions
Deploy & DevOps · Por Kevin Marcondes · Publicado el · Actualizado el · 8 min de lectura
Este tutorial muestra, paso a paso, cómo configurar un pipeline de despliegue automático con GitHub Actions. Crearás un workflow YAML que ejecuta pruebas, hace build con Docker, publica la imagen en GHCR y realiza el despliegue por SSH con Docker Compose. El ejemplo es genérico y puede adaptarse a distintas stacks.
Requisitos previos
Necesitas un repositorio en GitHub y permiso para crear workflows. En el servidor de destino, ten instalados Docker y Docker Compose, acceso por SSH y puertos abiertos en el firewall. Crea un endpoint de healthcheck en tu aplicación. Define un dominio y apunta el DNS al servidor si vas a exponerla en la web.
Si tu proyecto aún no tiene pruebas automáticas, empieza por algo simple. Si usas código PHP heredado, revisa cómo iniciar pruebas gradualmente en código PHP legado.
- Repositorio en GitHub con rama principal (p. ej.: main)
- Servidor Linux con Docker y Docker Compose
- Acceso SSH con clave privada
- Endpoint /health o similar
- Dominio configurado (opcional)
1) Crear el workflow básico
Comenzaremos con un workflow mínimo que se ejecuta en push y pull request a la rama principal. Hará checkout del código y preparará el entorno del pipeline. A partir de este punto lo iremos ampliando para añadir pruebas, build y 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 dependencias y ejecutar pruebas
Como ejemplo usaremos Node.js. Adáptalo a tu stack. El cache acelera las instalaciones. Ejecuta pruebas en cada push y PR para evitar regresiones. Si todavía no tienes tests, valida al menos un lint o un build rápido.
Para proyectos PHP/Laravel, hay una guía específica sobre 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 de la imagen Docker y push al GHCR
Empaquetar la aplicación en Docker estandariza el despliegue. Publicaremos en GitHub Container Registry (GHCR) usando el GITHUB_TOKEN. Así evitas crear registries externos.
Si estás decidiendo entre contenedores y máquinas virtuales para producción, compara pros y contras en Docker vs máquinas virtuales.
- Tags inmutables (SHA) facilitan trazabilidad y rollback
- Define 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 el servidor con Docker Compose
En el servidor usaremos Docker Compose para orquestar la aplicación. El archivo abajo referencia la imagen del GHCR y usa las variables REPO_IMAGE e IMAGE_TAG, que enviaremos en el deploy.
Atención: ejecutar Compose con la nueva imagen reiniciará el servicio. Usa ventanas de mantenimiento o técnicas blue/green para evitar indisponibilidad.
# 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 por SSH usando Docker Compose
El job de deploy se conecta por SSH, envía las variables de versión y ejecuta Docker Compose en el servidor. También realiza login en GHCR para poder tirar la imagen privada.
Almacena claves y tokens como secretos en el repositorio. Nunca comites secretos en 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 versión
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) Secretos, entornos y aprobaciones
Usa GitHub Environments para aislar secretos por entorno y exigir aprobación humana antes del deploy a producción. Define los secretos en el environment correspondiente y marca revisores obligatorios. En el YAML solo referencia el nombre del entorno. La aprobación se gestiona fuera del YAML, en la configuración del repositorio.
- Crea environments: staging y production
- Almacena secretos por entorno
- Exige aprobación para production
- Configura notificaciones (Slack, e‑mail) si es necesario
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) Cómo comprobar que funcionó
Valida el healthcheck y verifica los contenedores en ejecución. Confirma la versión por el tag/commit expuesto en la aplicación, si lo tienes. Automatiza una comprobación simple al final del deploy para fallar pronto en caso de error.
- Healthcheck HTTP 200
- Contenedor saludable en Docker
- Logs sin errores 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 comunes y soluciones
Aquí algunos errores frecuentes y cómo resolverlos. Evita cambios manuales en el servidor que no estén descritos como código: eso complica la reproducibilidad y el rollback. Para migraciones de base de datos, opta por prácticas seguras para evitar indisponibilidad.
- Host key verification failed: ejecuta ssh-keyscan para poblar known_hosts.
- Permissão negada no GHCR: confirma GHCR_USER y GHCR_TOKEN con el scope read:packages.
- docker compose: comando ausente? Instala el plugin/CLI de Compose en el servidor.
- Timeout en el healthcheck: revisa puertos y firewall; valida la ruta /health.
- Rollback: ejecuta docker compose up -d con la tag anterior (SHA anterior).
- Migraciones sin downtime: revisa versionado de bases de datos y migraciones sin downtime con CDC.
9) Próximos pasos y mejoras
Puedes añadir matrices de versiones, análisis estático y escaneo de vulnerabilidades. Para pipelines más grandes, audita tu proceso y aborda cuellos de botella, colas largas y puntos únicos de fallo. Cuando el despliegue sea crítico para el negocio, invierte en observabilidad y revisiones periódicas del pipeline.
- Matrix de versiones (Node, Python, PHP)
- Builds paralelos por servicio (monorepo)
- Escaneo SCA/Container
- Despliegues blue/green o canary
- Observabilidad y alertas
- Auditoría de procesos de deploy
Preguntas frecuentes
¿Necesito usar Docker para hacer deploy con GitHub Actions?
No. Puedes usar rsync para artefactos (build estático) o publicar paquetes e instalarlos en el servidor. Docker estandariza el runtime y facilita el rollback, pero no es obligatorio. Adapta el job de deploy a tu proceso actual (systemd, PM2, etc.).
¿Cómo hago un rollback rápido si la versión dio problema?
Usa tags inmutables con SHA. En el servidor, ejecuta Docker Compose fijando la tag anterior y aplica “docker compose up -d”. Mantén un historial corto de imágenes locales para acelerar el retorno y supervisa el healthcheck tras la reversión.
¿Cómo protejo secretos en el pipeline?
Almacena claves y tokens en los Secrets del repositorio o del Environment; nunca en el código. Restringe permisos, rota claves y utiliza entornos con aprobación para producción. Limita logs para no exponer valores sensibles durante el pipeline.
¿Puedo tener múltiples entornos (staging y producción)?
Sí. Crea dos environments y dos jobs de deploy o un único job parametrizado por un input manual (workflow_dispatch). Almacena secretos por entorno y permite el despliegue a producción solo tras la aprobación y la verificación en staging.
¿Y las migraciones de base de datos en el deploy?
Planifica migraciones con compatibilidad hacia atrás. Aplica primero cambios que no rompan la versión actual. Ejecuta migraciones en el pipeline con pasos idempotentes, monitorea el impacto y, cuando sea necesario, adopta estrategias de zero‑downtime con CDC.
¿Quieres ir más allá de lo básico del pipeline?
Si tu despliegue requiere estrategias como blue/green, canary, migraciones sensibles u observabilidad, evoluciona la arquitectura y el proceso con revisiones técnicas y automaciones a medida.
Discutir mejoras de CI/CDVer también
- Tutorial: implementar Canary releases con Traefik en Docker Compose usando headers y weighted routing para despliegues graduales de aplicaciones PHP
- Tutorial: configurar HAProxy con Let's Encrypt y healthchecks para balanceo de tráfico entre contenedores Docker en VPS (paso a paso)
- Tutorial práctico: implementar mTLS entre Nginx y microservicios usando smallstep CA en Docker Compose
- Tutorial: configurar cgroup v2 y systemd ResourceControl para limitar CPU, memoria e I/O de contenedores Docker en Debian