¿Cuántas veces has olvidado ejecutar los tests antes de hacer push? ¿Te has encontrado desplegando manualmente a producción a horas que no imaginabas? ¿Has tenido que volver atrás porque alguien del equipo rompió algo en main? Si has respondido sí a alguna de estas preguntas, GitHub Actions es la solución que necesitas.

GitHub Actions es el sistema de CI/CD (Continuous Integration/Continuous Deployment) integrado directamente en GitHub. Lo que hace especial a GitHub Actions no es solo que está perfectamente integrado con tu repositorio, sino que te permite automatizar prácticamente cualquier aspecto de tu workflow de desarrollo: desde ejecutar tests y validar code quality, hasta desplegar aplicaciones y publicar paquetes.

En este artículo vamos a ir desde cero hasta tener workflows completamente funcionales. Te mostraré cómo crear tu primer workflow, entenderás los conceptos fundamentales, verás ejemplos reales con Python, y al final tendrás las herramientas para automatizar tu propio proyecto. No necesitas experiencia previa con CI/CD, solo ganas de mejorar tu forma de trabajar.

Conceptos fundamentales

Antes de escribir código, necesitas entender cómo funciona GitHub Actions. La buena noticia es que todo gira alrededor de conceptos bastante intuitivos.

Workflows

Un workflow es básicamente un proceso automatizado que defines en un archivo YAML. Este archivo vive en .github/workflows/ en tu repositorio y contiene todas las instrucciones sobre qué hacer, cuándo hacerlo y cómo hacerlo. Puedes tener múltiples workflows en un mismo repositorio, cada uno con su propósito específico.

Events

Los events son los disparadores que inician tus workflows. Cuando ocurre un evento específico en tu repositorio, GitHub Actions puede ejecutar automáticamente uno o más workflows. Los eventos más comunes son:

  • push: Cuando alguien hace push de commits a una rama

  • pull_request: Cuando se abre, actualiza o cierra un pull request

  • schedule: Para ejecutar workflows en horarios específicos (como un cron)

  • workflow_dispatch: Para ejecutar workflows manualmente desde la UI de GitHub

  • release: Cuando publicas una nueva release

Pero hay muchísimos más: issues, comentarios, stars, forks... prácticamente cualquier acción en GitHub puede ser un evento.

Jobs

Un workflow está compuesto por uno o más jobs. Los jobs son unidades de trabajo que se ejecutan en paralelo por defecto, aunque puedes configurar dependencias entre ellos. Cada job se ejecuta en un runner fresco, lo que significa que tiene su propio entorno aislado.

Steps

Dentro de cada job tienes steps, que son los comandos o acciones individuales que se ejecutan secuencialmente. Un step puede ser tan simple como ejecutar un comando de bash, o puede usar una action predefinida del marketplace.

Actions

Las actions son bloques reutilizables de código que realizan tareas específicas. GitHub tiene un marketplace enorme de actions creadas por la comunidad. Por ejemplo, hay actions para hacer checkout de tu código, configurar Python en distintas versiones, cachear dependencias, desplegar a diferentes plataformas, etc. También puedes crear tus propias actions.

Runners

Los runners son las máquinas donde se ejecutan tus jobs. GitHub proporciona runners hosteados con Ubuntu, Windows y macOS, completamente gratis para repositorios públicos (con límites para privados). También puedes usar self-hosted runners si necesitas un entorno específico o más control.

¿Cómo se conecta todo?

Piénsalo así: cuando ocurre un event (como un push), GitHub ejecuta tu workflow. Ese workflow tiene uno o más jobs que corren en runners. Cada job tiene varios steps que pueden ejecutar comandos o usar actions. Simple, ¿verdad?

Tu primer Workflow

Vamos a crear nuestro primer workflow de Continuous Integration para un proyecto Python. Este workflow ejecutará tests automáticamente cada vez que hagas push o abras un pull request.

Imagina que tienes un proyecto Python con esta estructura:

mi-proyecto/
├── src/
│   └── calculator.py
├── tests/
│   └── test_calculator.py
├── requirements.txt
└── .github/
    └── workflows/
        └── ci.yml

Aquí está el contenido del workflow .github/workflows/ci.yml:

name: CI Pipeline

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

jobs:
  test:
    runs-on: ubuntu-latest
    
    steps:
    - name: Checkout code
      uses: actions/checkout@v4
      
    - name: Set up Python
      uses: actions/setup-python@v5
      with:
        python-version: '3.11'
        
    - name: Install dependencies
      run: |
        python -m pip install --upgrade pip
        pip install -r requirements.txt
        pip install pytest pytest-cov
        
    - name: Run tests
      run: |
        pytest tests/ -v --cov=src --cov-report=term-missing
        
    - name: Upload test results
      if: always()
      uses: actions/upload-artifact@v4
      with:
        name: pytest-results
        path: test-results/
        retention-days: 30

Desglosamos

Vamos a analizar cada sección:

name: CI Pipeline - Es el nombre que aparecerá en la UI de GitHub. Hazlo descriptivo.

on: - Define cuándo se ejecuta el workflow. En este caso:

  • push a las ramas main y develop

  • Cualquier pull_request contra main

jobs: - Aquí definimos los trabajos. Tenemos uno llamado test.

runs-on: ubuntu-latest - Le dice a GitHub que use un runner con Ubuntu. Otras opciones son windows-latest y macos-latest.

steps: - Los pasos que se ejecutan en orden:

  1. Checkout code: Usa la action actions/checkout@v4 para descargar tu código al runner. Sin esto, el runner estaría vacío.

  2. Set up Python: Usa actions/setup-python@v5 para instalar Python 3.11. Puedes especificar la versión que necesites.

  3. Install dependencies: Ejecuta comandos de bash para instalar dependencias. Actualiza pip y luego instala tus requirements más pytest.

  4. Run tests: Ejecuta pytest con coverage. El flag -v da output verbose, --cov=src mide coverage del directorio src, y --cov-report=term-missing muestra las líneas sin cubrir.

  5. Upload test results: Sube los resultados de tests como artifacts. El if: always() garantiza que se ejecute incluso si los tests fallan, permitiéndote descargar los resultados para análisis. Los artifacts se retienen 30 días.

Cómo ver los resultados

Una vez que hagas commit de este archivo y lo pushees a GitHub, ve a la pestaña "Actions" de tu repositorio. Ahí verás tu workflow ejecutándose. Puedes hacer click para ver los logs de cada step en tiempo real.

Si algún test falla, el workflow marcará el commit con una X roja. Si todo pasa, verás un check verde. En pull requests, esto es especialmente útil porque puedes configurar que no se permita mergear si los checks no pasan.

Los artifacts subidos (como los resultados de tests) estarán disponibles para descargar desde la página del workflow, lo cual es útil para analizar fallos o generar reportes.

Casos de uso intermedios

Ahora que entiendes lo básico, vamos a ver casos de uso más potentes.

Ejemplo 1: Deployment Automático

Este workflow despliega automáticamente tu aplicación cuando creas un tag de versión:

name: Deploy to Production

on:
  push:
    tags:
      - 'v*'  # Ejecuta cuando pusheas tags como v1.0.0
  workflow_dispatch:  # Permite ejecución manual
    inputs:
      environment:
        description: 'Environment to deploy'
        required: true
        type: choice
        options:
          - staging
          - production

jobs:
  deploy:
    runs-on: ubuntu-latest
    
    steps:
    - uses: actions/checkout@v4
    
    - name: Set up Python
      uses: actions/setup-python@v5
      with:
        python-version: '3.11'
    
    - name: Install dependencies
      run: |
        pip install -r requirements.txt
        pip install build twine
    
    - name: Build package
      run: python -m build
    
    - name: Publish to PyPI
      env:
        TWINE_USERNAME: __token__
        TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }}
      run: twine upload dist/*
    
    - name: Deploy to server
      env:
        SSH_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
        SSH_USER: ${{ secrets.SSH_USER }}
        SERVER_HOST: ${{ secrets.SERVER_HOST }}
      run: |
        echo "$SSH_KEY" > key.pem
        chmod 600 key.pem
        ssh -i key.pem -o StrictHostKeyChecking=no $SSH_USER@$SERVER_HOST << 'ENDSSH'
          cd /var/www/myapp
          git pull origin main
          source venv/bin/activate
          pip install -r requirements.txt
          sudo systemctl restart myapp
        ENDSSH
        rm -f key.pem

Puntos clave:

  • Se ejecuta cuando pusheas tags que empiezan con 'v' o manualmente con workflow_dispatch

  • El workflow_dispatch permite ejecutar el workflow desde la UI de GitHub seleccionando el ambiente

  • Construye el paquete con python -m build

  • Publica a PyPI usando secrets para las credenciales

  • Se conecta por SSH al servidor usando heredoc (<< 'ENDSSH') para mayor robustez en comandos multilinea

  • Limpia el archivo key.pem después de usarlo por seguridad

  • Usa $SSH_USER como secret en lugar de hardcodear el usuario

Ejemplo 2: Code Quality Checks

Un workflow completo que valida calidad de código usando múltiples herramientas:

name: Code Quality

on: [push, pull_request]

jobs:
  quality:
    runs-on: ubuntu-latest
    
    steps:
    - uses: actions/checkout@v4
    
    - name: Set up Python
      uses: actions/setup-python@v5
      with:
        python-version: '3.11'
    
    - name: Install tools
      run: |
        pip install black isort flake8 mypy pylint bandit
        pip install -r requirements.txt
    
    - name: Check code formatting with Black
      run: black --check src/
    
    - name: Check import sorting with isort
      run: isort --check-only src/
    
    - name: Lint with flake8
      run: flake8 src/ --max-line-length=100
    
    - name: Type check with mypy
      run: mypy src/ --ignore-missing-imports
    
    - name: Security check with Bandit
      run: bandit -r src/ -ll -f json -o bandit-report.json
      # -ll = solo reporta issues de severidad medium/high
      # Nota: No usamos continue-on-error porque queremos que 
      # el workflow falle si hay vulnerabilidades serias
    
    - name: Upload Bandit results
      if: always()
      uses: actions/upload-artifact@v4
      with:
        name: bandit-report
        path: bandit-report.json
        retention-days: 30

Este workflow ejecuta:

  • Black: Formatter de código

  • isort: Ordenador de imports

  • flake8: Linter de estilo

  • mypy: Type checker estático

  • Bandit: Scanner de seguridad con -ll para reportar solo severidades medium/high

Si alguna herramienta encuentra problemas, el workflow falla, obligándote a arreglar el código antes de mergear. No usamos continue-on-error: true en el security check porque queremos que las vulnerabilidades serias bloqueen el merge.

Trabajando con secrets y variables de entorno

Para manejar información sensible como API keys, tokens, o passwords, GitHub Actions tiene un sistema de secrets:

  1. Ve a Settings → Secrets and variables → Actions en tu repositorio

  2. Crea un nuevo secret (por ejemplo, PYPI_TOKEN)

  3. Úsalo en tu workflow con ${{ secrets.PYPI_TOKEN }}

Importante: Los secrets nunca se muestran en los logs. Si intentas hacer echo ${{ secrets.MI_SECRET }}, GitHub automáticamente lo censurará mostrando ***.

Para variables de entorno que no son sensibles, puedes usar el bloque env::

jobs:
  build:
    runs-on: ubuntu-latest
    env:
      DATABASE_URL: postgresql://localhost/mydb
      DEBUG: false
    
    steps:
    - name: Print environment
      run: |
        echo "Database: $DATABASE_URL"
        echo "Debug mode: $DEBUG"

Tips y mejores prácticas

Cachear dependencias para acelerar builds

Instalar dependencias en cada ejecución es lento. Puedes cachear los paquetes de pip:

- name: Cache pip packages
  uses: actions/cache@v4
  with:
    path: ~/.cache/pip
    key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
    restore-keys: |
      ${{ runner.os }}-pip-

- name: Install dependencies
  run: |
    python -m pip install --upgrade pip
    pip install -r requirements.txt

Esto puede reducir el tiempo de instalación de 2-3 minutos a apenas segundos en ejecuciones subsecuentes. El cache se invalida automáticamente cuando cambias requirements.txt gracias al uso de hashFiles().

Usar Actions del Marketplace vs Scripts Custom

El GitHub Actions Marketplace tiene miles de actions listas para usar. Antes de escribir un script custom, busca si ya existe una action:

  • actions/setup-python@v5: Configura Python

  • codecov/codecov-action@v4: Sube coverage a Codecov

  • pypa/gh-action-pypi-publish@release/v1: Publica a PyPI

  • docker/build-push-action@v5: Construye y pushea imágenes Docker

Usar actions del marketplace:

  • Ahorra tiempo de desarrollo

  • Está probado por la comunidad

  • Recibe actualizaciones de seguridad

  • Tiene mejor manejo de errores

Pero si necesitas algo muy específico, no dudes en escribir tu propio script.

Costes y límites de uso

Para repositorios públicos, GitHub Actions es completamente gratis con minutos ilimitados.

Para repositorios privados, tienes minutos gratis dependiendo de tu plan:

  • Free: 2,000 minutos/mes

  • Pro: 3,000 minutos/mes

  • Team: 3,000 minutos/mes

  • Enterprise: 50,000 minutos/mes

Los multiplicadores por runner son:

  • Ubuntu: 1x

  • Windows: 2x

  • macOS: 10x

Así que 10 minutos en macOS = 100 minutos de tu cuota.

Límites importantes a tener en cuenta:

  • Timeout máximo de job: 6 horas

  • Timeout máximo de workflow: 72 horas (35 días para self-hosted runners)

  • Tamaño máximo de logs: 64 KB por step de output

  • Artifacts: 500 MB por artifact individual, 10 GB total por workflow

  • Cache: 10 GB total por repositorio

Puedes revisar tu uso en Settings → Billing → Plans and usage.

Recursos de interés

Documentación oficial

La documentación de GitHub Actions es excelente y siempre actualizada. Aquí están los recursos esenciales:

Marketplace

Algunas actions imprescindibles para proyectos Python:

  • actions/checkout@v4: Checkout de código (casi siempre la necesitas como primer step)

  • actions/setup-python@v5: Configura Python en cualquier versión, con soporte para Poetry y pipenv

  • actions/cache@v4: Cachea dependencias para acelerar builds dramáticamente

  • actions/upload-artifact@v4: Sube archivos generados (reports, builds) para descargar después

  • actions/download-artifact@v4: Descarga artifacts de jobs anteriores

  • codecov/codecov-action@v4: Reporta coverage a Codecov con gráficas bonitas

  • github/codeql-action: Análisis automático de seguridad y vulnerabilidades

  • snok/install-poetry@v1: Instala y configura Poetry con caching inteligente

  • pre-commit/[email protected]: Ejecuta pre-commit hooks en CI

Explora más en el marketplace: https://github.com/marketplace?type=actions

Ejemplos de repos con workflows interesantes

Estudiar workflows de proyectos reales es la mejor forma de aprender patrones avanzados:

Conclusión

GitHub Actions transforma completamente cómo trabajamos. Lo que antes requería configurar Jenkins, mantener servidores CI, o ejecutar comandos manualmente, ahora es un archivo YAML en tu repo que funciona automáticamente.

La automatización no es solo sobre ahorrar tiempo, aunque ganarás horas cada semana. Es sobre algo mucho más valioso: paz mental.

Aquí va mi consejo, de desarrollador a desarrollador: no intentes implementar todo de golpe. Empieza con un workflow básico que ejecute tus tests. Solo eso. Úsalo una semana y verás cómo cambia tu forma de trabajar. Después, cuando ya sea parte de tu rutina, añade code quality checks. Y cuando eso se sienta natural, automatiza tus deployments. La mejor forma de adoptar GitHub Actions es paso a paso, dejando que cada mejora se asiente antes de añadir la siguiente.

El mejor momento para empezar es justamente ahora. Abre tu proyecto, crea .github/workflows/ci.yml, copia el ejemplo básico de este artículo, ajusta un par de líneas, y haz commit. En 10 minutos estarás viendo tu primer workflow ejecutándose. En una semana no querrás volver a trabajar sin él.

¿Vas a encontrarte con errores? Seguro. ¿Vas a tener que consultar la documentación? Sin duda. ¿Vas a aprender en el camino? Absolutamente. GitHub Actions no es algo que aprendes de memoria, es algo que vas dominando con la práctica.

Empieza pequeño. Tu primer workflow no tiene que ser perfecto, solo tiene que existir. Y cuando veas ese primer check verde, cuando sientas esa primera victoria de haber automatizado algo que antes hacías manualmente, vas a entender por qué GitHub Actions cambió la forma en que millones de desarrolladores trabajamos.

Tu yo del futuro, te lo agradecerá.

Nos vemos en la pestaña Actions.