Juan Diego Andrés PRADA··RAMÍREZ Entrar
Lección 4 de 7

CI/CD con GitHub Actions

¿Qué es?

CI/CD son dos siglas que van juntas. CI (Integración Continua) significa que cada vez que subes código, un robot lo descarga, instala dependencias y corre tus pruebas automáticamente. CD (Entrega/Despliegue Continuo) significa que, si esas pruebas pasan, ese mismo robot construye la imagen y la lleva a producción sin que tú toques nada. GitHub Actions es el motor que usaremos: ejecuta tareas (workflows) en respuesta a eventos de tu repositorio, como un push o un Pull Request.

¿Cómo funciona?

Defines un archivo YAML dentro de .github/workflows/. Ese archivo describe:

  • un evento disparador (on:) — por ejemplo, "cuando alguien haga push a main";
  • uno o más trabajos (jobs:) que corren en máquinas virtuales limpias que GitHub te presta gratis (los runners);
  • pasos (steps:) dentro de cada trabajo: descargar el código, instalar Python, correr pruebas, construir la imagen, desplegar.

Cuando ocurre el evento, GitHub arranca un runner desde cero, ejecuta los pasos en orden y te muestra en verde o rojo si todo salió bien. Si un paso falla, los siguientes no se ejecutan. Los secretos (contraseñas, llaves SSH) se guardan cifrados en GitHub y se inyectan como variables sin aparecer nunca en los logs.

¿Para qué sirve?

Sirve para que nadie pueda romper main sin enterarse, para no depender de "el que sabe desplegar" y para que pasar a producción sea tan simple como hacer merge. En este curso es el pegamento del despliegue: conecta lo que dockerizamos con el servidor que configuraremos después. Cuando termines este módulo, cada push a main probará tu API, construirá la imagen y la subirá a un registro, lista para desplegarse.

Pieza del despliegue que construye este módulo

Construimos dos workflows: uno de CI que corre las pruebas en cada Pull Request, y uno de CD que, al hacer merge a main, construye la imagen Docker y la publica en GitHub Container Registry (GHCR).

Paso 1: una prueba que el robot pueda correr

# tests/test_api.py
from fastapi.testclient import TestClient
from app.main import app

client = TestClient(app)

def test_saludo():
 respuesta = client.get("/saludo")
 assert respuesta.status_code == 200
 assert respuesta.json() == {"mensaje": "Hola desde el contenedor"}

def test_health():
 assert client.get("/health").status_code == 200

Agrega pytest==8.2.0 y httpx==0.27.0 a un requirements-dev.txt.

Paso 2: workflow de CI (pruebas en cada Pull Request)

# .github/workflows/ci.yml
name: CI

on:
 pull_request:
 branches: [main]
 push:
 branches: [main]

jobs:
 pruebas:
 runs-on: ubuntu-latest
 steps:
 - name: Descargar el código
 uses: actions/checkout@v4

 - name: Instalar Python
 uses: actions/setup-python@v5
 with:
 python-version: "3.12"

 - name: Instalar dependencias
 run: |
 pip install -r requirements.txt
 pip install -r requirements-dev.txt

 - name: Correr pruebas
 run: pytest -v

Fija siempre las acciones a una versión mayor (@v4), no a una rama. Una etiqueta como @main puede cambiar bajo tus pies y romper tu pipeline sin que tú hayas tocado nada.

Paso 3: workflow de CD (construir y publicar la imagen al hacer merge)

# .github/workflows/cd.yml
name: CD

on:
 push:
 branches: [main]

jobs:
 construir-y-publicar:
 runs-on: ubuntu-latest
 permissions:
 contents: read
 packages: write # necesario para publicar en GHCR
 steps:
 - uses: actions/checkout@v4

 - name: Iniciar sesión en GHCR
 uses: docker/login-action@v3
 with:
 registry: ghcr.io
 username: ${{ github.actor }}
 password: ${{ secrets.GITHUB_TOKEN }} # token que GitHub provee solo

 - name: Construir y subir imagen
 uses: docker/build-push-action@v6
 with:
 context: .
 push: true
 tags: |
 ghcr.io/${{ github.repository }}:latest
 ghcr.io/${{ github.repository }}:${{ github.sha }}

El tag con ${{ github.sha }} etiqueta la imagen con el hash exacto del commit, así siempre sabes qué versión está corriendo en producción.

Nunca escribas contraseñas o llaves directamente en el YAML. Cualquiera con acceso al repo las vería en el historial. Usa Settings → Secrets and variables → Actions y refiérelas con ${{ secrets.NOMBRE }}.

Paso 4: protege main para que CI sea obligatorio

En Settings → Branches → Add rule activa "Require status checks to pass before merging" y selecciona el job pruebas. Ahora nadie puede fusionar a main con las pruebas en rojo.

Tech English: workflow = flujo de trabajo automatizado; runner = la máquina que ejecuta el flujo; job = trabajo; artifact = archivo resultante de un job; secret = secreto cifrado; to trigger = disparar.

Ejercicios

  1. CI que falla cuando debe. Introduce un cambio que rompa una prueba a propósito en una rama, abre un Pull Request y muestra (captura) que GitHub Actions bloquea el merge. Luego corrígelo y muestra el check en verde. Se evalúa que la protección de rama impida realmente fusionar con pruebas rotas.

  2. Imagen publicada por hash. Haz que un push a main publique la imagen en GHCR con dos tags: latest y el SHA del commit. Se evalúa que la imagen aparezca en el registro y que docker pull ghcr.io/tu-usuario/mi-api:latest funcione.

Tu progreso se guarda en este navegador. Inicia sesión para guardarlo en tu cuenta y verlo desde cualquier dispositivo.