DEV Community

Jesus Oviedo Riquelme
Jesus Oviedo Riquelme

Posted on

LLPY-13: CI/CD con GitHub Actions - Automatización Completa

🎯 El Desafío de los Deployments Manuales

Imagina el proceso tradicional de deployment:

1. Developer hace cambios → 10 min
2. Run tests localmente → 5 min
3. Fix issues → 15 min
4. Build Docker image → 5 min
5. Push a Docker Hub → 3 min
6. SSH a servidor → 2 min
7. Pull nueva imagen → 2 min
8. Restart servicio → 1 min
9. Verificar deployment → 3 min
10. Rollback si falla → 10 min

TOTAL: 56 minutos + posibilidad de error humano
Enter fullscreen mode Exit fullscreen mode

Problemas:

  • ⏱️ Tiempo desperdiciado: 1 hora por deploy
  • 🐛 Errores humanos: Olvidar un paso, typo en comando
  • 📝 Sin tracking: ¿Quién deployó qué, cuándo?
  • 🔄 Inconsistencia: Diferentes procesos por developer
  • 🚨 Sin rollback fácil: Revertir requiere proceso manual

📊 La Magnitud del Problema

Requisitos de CI/CD Moderno

  1. ✅ Quality Gates: Tests, linting, security scan automáticos
  2. 🏗️ Build Automation: Docker images con cada cambio
  3. 📦 Artifact Management: Versionado y storage de images
  4. 🚀 Auto Deployment: Deploy a staging/prod automático
  5. 🔍 Validation: Health checks post-deployment
  6. 📊 Notifications: Alertas de successes/failures
  7. 🔄 Rollback: Fácil volver a versión anterior
  8. 🔐 Secrets Management: API keys, credentials seguros

7 Workflows del Proyecto

# Workflow Trigger Propósito Duración
1 code-quality.yml Push/PR Tests, linting, security 3-5 min
2 docker-processing-build-publish.yml Push/Manual Build processing image 4-6 min
3 docker-api-build-publish.yml Push/Manual Build API image 5-7 min
4 terraform-apply-on-tf-change.yml Push .tf files Apply infra changes 3-8 min
5 deploy-qdrant.yml Manual/Push Deploy Qdrant to VM 5-10 min
6 update-batch-job-image.yml Manual Update batch job image 2-3 min
7 update-api-secrets-deploy.yml Manual Update secrets + deploy API 3-5 min

💡 La Solución: GitHub Actions

¿Qué es GitHub Actions?

GitHub Actions es una plataforma de CI/CD integrada en GitHub que permite:

  • 🤖 Automatización: Workflows como código (YAML)
  • Trigger flexible: Push, PR, schedule, manual
  • 🔧 Actions marketplace: Miles de actions reutilizables
  • 🔐 Secrets management: Secrets encriptados integrados
  • 📊 Logging: Logs detallados de cada ejecución
  • 💰 Free tier: 2000 minutos/mes para repos públicos

Arquitectura de un Workflow

name: Mi Workflow               # Nombre del workflow
on: [push, pull_request]        # Triggers
jobs:                           # Jobs (paralelos por defecto)
  build:                        # Job 1
    runs-on: ubuntu-latest      # Runner
    steps:                      # Steps (secuenciales)
      - uses: actions/checkout@v4    # Action del marketplace
      - run: echo "Hello"            # Comando shell
      - name: Build
        run: docker build -t app .   # Step con nombre
Enter fullscreen mode Exit fullscreen mode

🚀 Workflow 1: Code Quality & Style

Archivo .github/workflows/code-quality.yml:

name: Code Quality & Style

on:
  push:
    branches: ['**']
    paths:
      - 'src/**/*.py'
      - 'utils/**/*.py'
      - 'tests/**/*.py'
      - '.pre-commit-config.yaml'
      - 'pyproject.toml'

  pull_request:
    branches: ['**']
    paths:
      - 'src/**/*.py'
      - 'utils/**/*.py'

jobs:
  # Job 1: Pre-commit hooks
  pre-commit:
    name: Pre-Commit Hooks
    runs-on: ubuntu-latest

    steps:
    - name: Checkout code
      uses: actions/checkout@v5
      with:
        fetch-depth: 0

    - name: Set up Python
      uses: actions/setup-python@v6
      with:
        python-version: '3.13'

    - name: Install UV
      uses: astral-sh/setup-uv@v6
      with:
        version: latest

    - name: Install pre-commit
      run: uv tool install pre-commit

    - name: Cache pre-commit hooks
      uses: actions/cache@v4
      with:
        path: ~/.cache/pre-commit
        key: pre-commit-${{ runner.os }}-${{ hashFiles('.pre-commit-config.yaml') }}

    - name: Run pre-commit hooks
      run: uv tool run pre-commit run --all-files --show-diff-on-failure

  # Job 2: Lint API code
  lint-api:
    name: Lint API Code
    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v5

    - name: Set up Python
      uses: actions/setup-python@v6
      with:
        python-version: '3.13'

    - name: Install UV
      uses: astral-sh/setup-uv@v6

    - name: Install ruff
      run: uv tool install ruff

    - name: Run ruff linter
      run: uv tool run ruff check src/lus_laboris_api --output-format=github

  # Job 3: Security scan
  security-scan:
    name: Security Scan (Bandit)
    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v5

    - name: Set up Python
      uses: actions/setup-python@v6
      with:
        python-version: '3.13'

    - name: Install UV
      uses: astral-sh/setup-uv@v6

    - name: Install bandit
      run: uv tool install bandit

    - name: Run bandit security scan
      run: uv tool run bandit -r src/ utils/ -f github

  # Job 4: Type checking
  type-check:
    name: Type Checking (MyPy)
    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v5

    - name: Set up Python
      uses: actions/setup-python@v6
      with:
        python-version: '3.13'

    - name: Install UV
      uses: astral-sh/setup-uv@v6

    - name: Install dependencies
      run: |
        cd src/lus_laboris_api
        uv sync

    - name: Run mypy
      run: |
        cd src/lus_laboris_api
        uv run mypy api/ --ignore-missing-imports

  # Job 5: Tests
  test-api:
    name: API Tests (PyTest)
    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v5

    - name: Set up Python
      uses: actions/setup-python@v6
      with:
        python-version: '3.13'

    - name: Install UV
      uses: astral-sh/setup-uv@v6

    - name: Install dependencies
      run: |
        cd src/lus_laboris_api
        uv sync

    - name: Run tests with coverage
      run: |
        cd src/lus_laboris_api
        uv run pytest tests/ --cov=api --cov-report=term --cov-report=html

    - name: Upload coverage reports
      uses: actions/upload-artifact@v4
      with:
        name: coverage-report
        path: src/lus_laboris_api/htmlcov/
Enter fullscreen mode Exit fullscreen mode

Quality Gates:

  • Pre-commit: Formatting, trailing whitespace, YAML syntax
  • Linting: Ruff (10-100x más rápido que flake8)
  • Security: Bandit detecta vulnerabilidades
  • Type safety: MyPy valida type hints
  • Tests: PyTest con coverage report

Triggers:

  • Push a cualquier branch que toque Python files
  • Pull requests

Resultado: Si algún job falla → PR bloqueado → Fix required

🐳 Workflows 2-3: Docker Build & Publish

Workflow 2: Processing Image

Archivo .github/workflows/docker-processing-build-publish.yml:

name: Build & Publish Docker Image (Processing)

on:
  workflow_dispatch:
  push:
    paths:
      - 'src/processing/Dockerfile'
      - 'src/processing/extract_law_text.py'
      - 'src/processing/pyproject.toml'
      - 'src/processing/.python-version'

jobs:
  build-and-push:
    runs-on: ubuntu-latest

    steps:
    - name: Checkout code
      uses: actions/checkout@v5

    - name: Set up Docker Buildx
      uses: docker/setup-buildx-action@v3

    - name: Login to Docker Hub
      uses: docker/login-action@v3
      with:
        username: ${{ secrets.DOCKER_HUB_USERNAME }}
        password: ${{ secrets.DOCKER_HUB_PASSWORD }}

    - name: Get date tag
      id: date_tag
      run: echo "tag=$(date +'%Y%m%d')" >> $GITHUB_OUTPUT

    - name: Build and push
      uses: docker/build-push-action@v6
      with:
        context: ./src/processing
        push: true
        tags: |
          ${{ secrets.DOCKER_HUB_USERNAME }}/lus-laboris-processing:latest
          ${{ secrets.DOCKER_HUB_USERNAME }}/lus-laboris-processing:${{ steps.date_tag.outputs.tag }}
Enter fullscreen mode Exit fullscreen mode

Workflow 3: API Image

Similar a workflow 2, pero para src/lus_laboris_api/

Ventajas:

  • Automated tagging: latest + fecha (20241016)
  • Trigger smart: Solo rebuild si Dockerfile o código cambia
  • Buildx: Soporte multi-platform (AMD64, ARM64)
  • Registry caching: Reutiliza layers de builds anteriores

🏗️ Workflow 4: Terraform Apply

Archivo .github/workflows/terraform-apply-on-tf-change.yml:

name: Terraform Apply on TF Change

on:
  push:
    paths:
      - 'terraform/**/*.tf'
      - 'terraform/**/*.tfvars'
  workflow_dispatch:

jobs:
  terraform:
    runs-on: ubuntu-latest
    env:
      GOOGLE_APPLICATION_CREDENTIALS: gcp-key.json

    steps:
    - name: Checkout code
      uses: actions/checkout@v4

    - name: Setup Terraform
      uses: hashicorp/setup-terraform@v3
      with:
        terraform_version: 1.9.0

    - name: Create GCP credentials file
      run: |
        echo '${{ secrets.GSA_KEY }}' > gcp-key.json

    - name: Create terraform.tfvars
      working-directory: ./terraform
      run: |
        # Generate tfvars from GitHub secrets/variables
        cat > terraform.tfvars <<EOF
        project_id = "${{ secrets.GCP_PROJECT_ID }}"
        region = "${{ secrets.GCP_REGION }}"
        bucket_name = "${{ vars.GCP_BUCKET_NAME }}"
        # ... (más variables)
        EOF

    - name: Terraform Init
      working-directory: ./terraform
      run: terraform init

    - name: Terraform Validate
      working-directory: ./terraform
      run: terraform validate

    - name: Terraform Plan
      working-directory: ./terraform
      run: terraform plan -out=tfplan

    - name: Terraform Apply
      working-directory: ./terraform
      run: terraform apply -auto-approve tfplan

    - name: Show Outputs
      working-directory: ./terraform
      run: terraform output -json
Enter fullscreen mode Exit fullscreen mode

Triggers:

  • Cambios en cualquier archivo .tf o .tfvars
  • Manual via workflow_dispatch

Proceso:

  1. ✅ Checkout código
  2. ✅ Setup Terraform
  3. ✅ Create credentials file
  4. ✅ Generate terraform.tfvars dinámicamente
  5. ✅ Init → Validate → Plan → Apply
  6. ✅ Show outputs (IPs, URLs)

Resultado: Infraestructura actualizada automáticamente en 3-8 minutos

🗄️ Workflow 5: Deploy Qdrant to VM

Archivo .github/workflows/deploy-qdrant.yml:

name: Deploy Qdrant to GCP VM

on:
  workflow_dispatch:
  push:
    paths:
      - 'services/vectordb/**'
      - 'terraform/modules/compute_engine/**'

jobs:
  deploy:
    runs-on: ubuntu-latest

    steps:
    - name: Checkout code
      uses: actions/checkout@v4

    - name: Authenticate to GCP
      uses: google-github-actions/auth@v2
      with:
        credentials_json: ${{ secrets.GSA_KEY }}

    - name: Set up Cloud SDK
      uses: google-github-actions/setup-gcloud@v2

    - name: Get VM details
      id: vm_info
      run: |
        VM_EXTERNAL_IP=$(gcloud compute instances describe ${{ vars.GCP_COMPUTE_ENGINE_VM_NAME }} \
          --zone=${{ vars.GCP_COMPUTE_ENGINE_VM_ZONE }} \
          --format='get(networkInterfaces[0].accessConfigs[0].natIP)')

        echo "vm_ip=${VM_EXTERNAL_IP}" >> $GITHUB_OUTPUT

    - name: Install Docker on VM
      run: |
        gcloud compute ssh ${{ vars.GCP_COMPUTE_ENGINE_VM_NAME }} \
          --zone=${{ vars.GCP_COMPUTE_ENGINE_VM_ZONE }} \
          --command="
            # Install Docker if not exists
            if ! command -v docker &> /dev/null; then
              curl -fsSL https://get.docker.com -o get-docker.sh
              sudo sh get-docker.sh
              sudo usermod -aG docker \$USER
            fi

            # Install Docker Compose
            sudo curl -L https://github.com/docker/compose/releases/latest/download/docker-compose-\$(uname -s)-\$(uname -m) \
              -o /usr/local/bin/docker-compose
            sudo chmod +x /usr/local/bin/docker-compose
          "

    - name: Deploy Qdrant
      run: |
        # Copy docker-compose.yml
        gcloud compute scp services/vectordb/docker-compose.yml \
          ${{ vars.GCP_COMPUTE_ENGINE_VM_NAME }}:~/docker-compose.yml \
          --zone=${{ vars.GCP_COMPUTE_ENGINE_VM_ZONE }}

        # Create .env with API key
        gcloud compute ssh ${{ vars.GCP_COMPUTE_ENGINE_VM_NAME }} \
          --zone=${{ vars.GCP_COMPUTE_ENGINE_VM_ZONE }} \
          --command="
            echo 'QDRANT_API_KEY=${{ secrets.QDRANT_API_KEY }}' > ~/.env

            # Start Qdrant
            docker-compose up -d

            # Wait and verify
            sleep 10
            curl -f http://localhost:6333/healthz || exit 1
          "

    - name: Verify deployment
      run: |
        curl -f http://${{ steps.vm_info.outputs.vm_ip }}:6333/collections
        echo "✅ Qdrant deployed successfully at ${{ steps.vm_info.outputs.vm_ip }}"
Enter fullscreen mode Exit fullscreen mode

Proceso:

  1. Get VM external IP
  2. Install Docker + Docker Compose (si no existe)
  3. Copy docker-compose.yml
  4. Create .env with API key
  5. docker-compose up -d
  6. Verify health check

Resultado: Qdrant running en VM con persistencia en 5-10 minutos

🚀 Workflow 6: Update Batch Job Image

Archivo .github/workflows/update-batch-job-image.yml:

name: Update Batch Job Image

on:
  workflow_dispatch:

jobs:
  update-job:
    runs-on: ubuntu-latest

    steps:
    - name: Authenticate to GCP
      uses: google-github-actions/auth@v2
      with:
        credentials_json: ${{ secrets.GSA_KEY }}

    - name: Set up Cloud SDK
      uses: google-github-actions/setup-gcloud@v2

    - name: Update Cloud Run Job image
      run: |
        IMAGE="${{ secrets.DOCKER_HUB_USERNAME }}/${{ vars.DOCKER_IMAGE_NAME_PROCESSING }}:${{ vars.GCP_CLOUD_RUN_BATCH_IMAGE_TAG }}"

        echo "🔄 Updating job image to: ${IMAGE}"

        gcloud run jobs update ${{ vars.GCP_CLOUD_RUN_BATCH_JOB_NAME }} \
          --image=${IMAGE} \
          --region=${{ secrets.GCP_REGION }} \
          --project=${{ secrets.GCP_PROJECT_ID }}

        echo "✅ Job updated successfully!"

    - name: Verify update
      run: |
        gcloud run jobs describe ${{ vars.GCP_CLOUD_RUN_BATCH_JOB_NAME }} \
          --region=${{ secrets.GCP_REGION }} \
          --project=${{ secrets.GCP_PROJECT_ID }} \
          --format='value(template.template.containers[0].image)'
Enter fullscreen mode Exit fullscreen mode

Uso:

  1. Build nueva imagen → Push a Docker Hub (workflow 2)
  2. Cambiar variable GCP_CLOUD_RUN_BATCH_IMAGE_TAG a nuevo tag
  3. Ejecutar workflow 6 manualmente
  4. Job actualizado en 2-3 minutos

🚀 Workflow 7: Update API Secrets & Deploy

Archivo .github/workflows/update-api-secrets-deploy.yml:

name: Update API Secrets & Deploy

on:
  workflow_dispatch:

jobs:
  update-and-deploy:
    runs-on: ubuntu-latest

    steps:
    - name: Checkout code
      uses: actions/checkout@v4

    - name: Authenticate to GCP
      uses: google-github-actions/auth@v2
      with:
        credentials_json: ${{ secrets.GSA_KEY }}

    - name: Set up Cloud SDK
      uses: google-github-actions/setup-gcloud@v2

    - name: Update Secrets in Secret Manager
      if: ${{ vars.GCP_CLOUD_SECRETS_UPDATE == 'true' }}
      run: |
        echo "📝 Updating secrets..."

        # Update .env secret
        echo "${{ secrets.API_ENV_FILE }}" | \
          gcloud secrets versions add ${{ vars.GCP_CLOUD_SECRETS_API_ENV_ID }} \
          --data-file=- \
          --project=${{ secrets.GCP_PROJECT_ID }}

        # Update JWT public key
        echo "${{ secrets.JWT_PUBLIC_KEY }}" | \
          gcloud secrets versions add ${{ vars.GCP_CLOUD_SECRETS_JWT_KEY_ID }} \
          --data-file=- \
          --project=${{ secrets.GCP_PROJECT_ID }}

        echo "✅ Secrets updated"

    - name: Deploy API to Cloud Run
      run: |
        IMAGE="${{ secrets.DOCKER_HUB_USERNAME }}/${{ vars.DOCKER_IMAGE_NAME_RAG_API }}:${{ vars.GCP_CLOUD_RUN_API_IMAGE_TAG }}"

        echo "🚀 Deploying ${IMAGE}..."

        gcloud run deploy ${{ vars.GCP_CLOUD_RUN_API_SERVICE_NAME }} \
          --image=${IMAGE} \
          --region=${{ secrets.GCP_REGION }} \
          --project=${{ secrets.GCP_PROJECT_ID }} \
          --port=8000 \
          --update-secrets="/app/secrets/env/.env=${{ vars.GCP_CLOUD_SECRETS_API_ENV_ID }}:latest,/app/secrets/jwt/public_key.pem=${{ vars.GCP_CLOUD_SECRETS_JWT_KEY_ID }}:latest" \
          --cpu=${{ vars.GCP_CLOUD_RUN_API_CPU }} \
          --memory=${{ vars.GCP_CLOUD_RUN_API_MEMORY }} \
          --min-instances=${{ vars.GCP_CLOUD_RUN_API_MIN_INSTANCES }} \
          --max-instances=${{ vars.GCP_CLOUD_RUN_API_MAX_INSTANCES }} \
          --timeout=300 \
          --allow-unauthenticated

        echo "✅ API deployed!"

    - name: Get Service URL
      run: |
        URL=$(gcloud run services describe ${{ vars.GCP_CLOUD_RUN_API_SERVICE_NAME }} \
          --region=${{ secrets.GCP_REGION }} \
          --format='value(status.url)' \
          --project=${{ secrets.GCP_PROJECT_ID }})

        echo "🌐 Service URL: ${URL}"
        echo "📋 Health Check: ${URL}/api/health"
        echo "📊 Swagger UI: ${URL}/docs"
Enter fullscreen mode Exit fullscreen mode

Proceso completo:

  1. (Opcional) Update secrets en Secret Manager
  2. Deploy nueva versión de la imagen
  3. Cloud Run hace rolling update (zero downtime)
  4. Obtener URL del servicio
  5. Mostrar health check y Swagger endpoints

🎯 Flujo Completo de CI/CD

Escenario: Nuevo Feature

Developer:
├─ 1. Crea branch: feature/add-reranking
├─ 2. Escribe código: reranking_service.py
├─ 3. Commit & push
│
GitHub Actions (automático):
├─ 4. [Workflow 1] Code Quality
│     ├─ Pre-commit ✅
│     ├─ Linting ✅
│     ├─ Security scan ✅
│     ├─ Type check ✅
│     └─ Tests ✅
│
Developer:
├─ 5. Create Pull Request
│
GitHub:
├─ 6. PR shows all checks passed ✅
│
Team:
├─ 7. Code review
├─ 8. Approve PR
├─ 9. Merge to main
│
GitHub Actions (automático):
├─ 10. [Workflow 3] Build API image
│      └─ Push: username/api:latest + :20241016
│
Developer:
├─ 11. Update variable: GCP_CLOUD_RUN_API_IMAGE_TAG=20241016
│
GitHub Actions (manual):
├─ 12. [Workflow 7] Deploy API
│      ├─ Update secrets (optional)
│      ├─ Deploy to Cloud Run
│      └─ Verify health check ✅
│
Production:
└─ 13. New feature live! 🎉

Total time: 15-20 minutos (automático)
Manual steps: 5 (branch, commit, PR, merge, trigger deploy)
Enter fullscreen mode Exit fullscreen mode

🧪 Testing Local con Act

¿Qué es Act?

Act permite ejecutar GitHub Actions workflows localmente con Docker:

# Instalar act
brew install act  # macOS
# o
curl https://raw.githubusercontent.com/nektos/act/master/install.sh | sudo bash
Enter fullscreen mode Exit fullscreen mode

Script Interactivo: act_menu.sh

Archivo .github/workflows/act_menu.sh:

#!/bin/bash

# Interactive menu for running GitHub Actions locally with act

echo "=========================================="
echo "    GitHub Actions Local Testing"
echo "=========================================="
echo ""
echo "1) Code Quality"
echo "2) Build Processing Image"
echo "3) Build API Image"
echo "4) Terraform Apply"
echo "5) Exit"
echo ""
read -p "Select workflow [1-5]: " option

case $option in
  1)
    act -j pre-commit -j lint-api -j security-scan
    ;;
  2)
    act -j build-and-push-processing \
      --secret-file ../../.env \
      -W docker-processing-build-publish.yml
    ;;
  3)
    act -j build-and-push-api \
      --secret-file ../../.env \
      -W docker-api-build-publish.yml
    ;;
  4)
    act -j terraform \
      --secret-file ../../.env \
      -W terraform-apply-on-tf-change.yml
    ;;
  5)
    exit 0
    ;;
esac
Enter fullscreen mode Exit fullscreen mode

Uso:

cd .github/workflows
chmod +x act_menu.sh
./act_menu.sh
Enter fullscreen mode Exit fullscreen mode

Ventajas de Act:

  • Test locally: Antes de push, verify workflow
  • Faster iteration: No wait for GitHub runners
  • Debug: Ver logs en tiempo real
  • Cost: Zero (no consume GitHub Actions minutes)

🎯 Casos de Uso Reales

Para Continuous Integration:

"Quiero asegurar calidad de código en cada PR"

Solución:

  • Workflow 1 (Code Quality) se ejecuta automáticamente
  • PR no se puede mergear si hay fallos
  • Code review con confianza de que quality checks pasaron

Para Continuous Deployment:

"Quiero deploy automático a staging en cada merge a main"

Solución:

on:
  push:
    branches: [main]

jobs:
  deploy-staging:
    runs-on: ubuntu-latest
    steps:
      # Build image
      # Push to registry
      # Deploy to Cloud Run (staging environment)
Enter fullscreen mode Exit fullscreen mode

Para Rollback Rápido:

"La última versión tiene un bug crítico"

Solución:

# 1. Cambiar IMAGE_TAG a versión anterior
GCP_CLOUD_RUN_API_IMAGE_TAG=20241015  # Era 20241016

# 2. Ejecutar workflow 7
# GitHub Actions → Deploy versión anterior

# 3. Verificar
curl https://api-url/api/health

# ✅ Rollback en 3 minutos
Enter fullscreen mode Exit fullscreen mode

Para Multi-Environment:

"Quiero deploy a dev, staging, prod con diferentes configs"

Solución:

# Workflow con matrix strategy
jobs:
  deploy:
    strategy:
      matrix:
        environment: [dev, staging, prod]

    steps:
      - name: Deploy to ${{ matrix.environment }}
        run: |
          gcloud run deploy api-${{ matrix.environment }} \
            --image=... \
            --set-env-vars="ENV=${{ matrix.environment }}"
Enter fullscreen mode Exit fullscreen mode

🚀 El Impacto Transformador

Antes de GitHub Actions:

  • ⏱️ Deploy time: 1 hora manual
  • 🐛 Error rate: 20-30% (typos, pasos olvidados)
  • 📝 Tracking: Notas en Slack, memoria
  • 🔄 Rollback: 30-60 minutos manual
  • 💰 Cost: Jenkins server ($50-100/mes)
  • 🧪 Testing: "Esperemos que funcione en prod"

Después de GitHub Actions:

  • Deploy time: 15-20 minutos automático
  • Error rate: <5% (proceso automatizado)
  • 📊 Tracking: Cada deploy en Git + GitHub UI
  • 🔄 Rollback: 3 minutos (cambiar tag + trigger)
  • 💰 Cost: $0 (free tier suficiente)
  • 🧪 Testing: Tests obligatorios antes de merge

Métricas de Mejora:

Aspecto Manual Con GitHub Actions Mejora
Deploy time 60 min 15 min -75%
Error rate 25% <5% -80%
Deploy frequency 1-2/semana 10-20/semana +900%
Rollback time 30-60 min 3 min -95%
Time to production Días Horas -90%

💡 Lecciones Aprendidas

1. Separate Workflows > Monolithic

7 workflows pequeños y específicos > 1 workflow gigante. Más fácil debug y maintain.

2. Secrets vs Variables

  • Secrets: API keys, passwords (encriptados)
  • Variables: Config pública (service names, regions)

3. Manual Triggers son Poder

workflow_dispatch permite control fino sobre deployments críticos.

4. Path Filters Ahorran Minutos

Solo ejecutar workflow si archivos relevantes cambiaron = menos tiempo desperdiciado.

5. Artifacts para Debugging

- uses: actions/upload-artifact@v4
  with:
    name: coverage-report
    path: htmlcov/
Enter fullscreen mode Exit fullscreen mode

Útil para ver coverage reports, logs, etc.

6. Act para Desarrollo de Workflows

Desarrollar workflows con act localmente = 10x más rápido que push-wait-check en GitHub.

🎯 El Propósito Más Grande

GitHub Actions no es solo CI/CD - es calidad automatizada. Al automatizar:

  • ✅ Quality: Tests obligatorios en cada cambio
  • 🏗️ Build: Imágenes consistentes y versionadas
  • 🚀 Deploy: Proceso reproducible y traceable
  • 🔍 Visibility: Cada ejecución logged y auditable
  • ⚡ Speed: De código a producción en minutos
  • 🛡️ Safety: Quality gates previenen broken deploys
  • 💰 Cost: Zero infrastructure, pay-per-use
  • 👥 Collaboration: Workflows versionados = team aligned

Estamos convirtiendo el deployment de un proceso manual propenso a errores en un pipeline automatizado confiable que entrega valor a producción múltiples veces por día.


🔗 Recursos y Enlaces

Repositorio del Proyecto

Documentación Técnica

Recursos Externos


Próximo Post: LLPY-14 - Evaluación y Métricas de Calidad

En el siguiente y último post de la serie, exploraremos cómo evaluar y medir la calidad del sistema RAG end-to-end, incluyendo ground truth datasets, métricas de retrieval y generation, LLM-as-a-judge evaluations, y monitoreo continuo en producción.

Top comments (0)