CI para Python usando GitHub Actions
Tener herramientas que nos ayuden a automatizar nuestro flujo de trabajo es extremadamente útil, y que además nos facilite incorporar otras herramientas para optimizar nuestros procesos de calidad y seguridad (entre otros) es impresionante, pero que adicional a ello sea gratis y muy fácil de hacer ya es otro nivel.
GitHub realmente ha cambiado la forma de automatizar el ciclo de vida de desarrollo de software en los últimos años, principalmente porque ha integrado un montón de herramientas que realmente ayudan a los desarrolladores, solo por mencionar algunas como VSCode, Code Server, Workspaces, ghcr, Copilot y por supuesto Actions con la que estaremos jugando un poco el día de hoy.
Qué viene a reemplazar GitHub Actions?
Pues depende mucho de como lo uses, pero principalmente viene a dejar un poco atrás a herramientas como Jenkins o CircleCI, principalmente porque ya esta aprovisionado, solo debes usarlo y además incluye un montón de integraciones que puedes ejecutar directamente en la infraestructura de GitHub sin tener que preocuparte casi que por nada.

En el marketplace vamos a encontrar un sin número de extensiones que permiten integrar con soluciones de terceros, pero también muchas de ellas que puedes ejecutar de forma contenerizada dentro de los recursos de GitHub sin pagar un solo dólar, y eso exactamente vamos a ver aquí sobre como usar herramientas gratuitas para construir un servicio desarrollado en Python 3.
Qué se hace generalmente en un proceso de CI?
Partamos que CI viene del Inglés Continuous Integration (o Integración continua en Español), y se refiere a una práctica DevOps que se ejecuta cada vez que un desarrollador integra código nuevo en el repositorio, ejecutando tareas como: construcción / compilación, pruebas, controles de calidad y seguridad, entre otros.
.github > workflows > ci-action.yml
Para iniciar usando GitHub Actions debemos crear una ruta similar a esta en la raíz de nuestro repositorio de código en GitHub, dónde usando el formato YAML vamos a definir un conjunto de pasos a realizar.

name: Python application CI
on:
push:
branches: ["main"]
pull_request:
branches: ["main"]
env:
REGISTRY: "ghcr.io"
IMAGE_NAME: ${{ github.repository }}
IMAGE_TAG: ${{ github.sha }}
permissions:
packages: write
contents: read
security-events: write
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python 3.${{ vars.PYTHON_MINOR_VERSION }}
uses: actions/setup-python@v3
with:
python-version: "3.${{ vars.PYTHON_MINOR_VERSION }}"
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install pip-tools
python -m piptools compile -o src/requirements.txt src/pyproject.toml
pip install flake8 pytest coverage
if [ -f src/requirements.txt ]; then pip install -r src/requirements.txt; fi
- name: Lint with flake8
run: |
# stop the build if there are Python syntax errors or undefined names
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
- name: Test with pytest
run: |
coverage run -m pytest
coverage report -m
- name: Build and push Docker images
uses: docker/build-push-action@v6.18.0
with:
context: ./
file: Dockerfile.actions
pull: true
build-args: |
PYTHON_VERSION=3.${{ vars.PYTHON_MINOR_VERSION }}
tags: |
"${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest"
"${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ env.IMAGE_TAG }}"
- name: Run 2ms Scan
run: docker run -v $(pwd):/repo checkmarx/2ms:2.8.1 git /repo
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@0.32.0
with:
image-ref: "${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ env.IMAGE_TAG }}"
format: "sarif"
output: "trivy-results.sarif"
ignore-unfixed: true
severity: "CRITICAL,HIGH"
- name: Upload Trivy scan results to GitHub Security tab
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: "trivy-results.sarif"
- name: Get changed files
id: changed-files
uses: tj-actions/changed-files@2f7c5bfce28377bc069a65ba478de0a74aa0ca32 #v46.0.1
with:
separator: ','
- name: Vorpal with reviewdog
if: ${{ steps.changed-files.outputs.all_changed_files != '' }}
uses: checkmarx/vorpal-reviewdog-github-action@v1.0.0
with:
source_path: "${{ steps.changed-files.outputs.all_changed_files }}"
filter_mode: file
github_token: ${{ secrets.github_token }}
reporter: github-pr-check
level: error
fail_on_error: false
- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }} # Or your PAT secret
- name: push image to ghcr.io
run: docker push "${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}" --all-tags
En este ejemplo tenemos algunas secciones que me gustaría destacar:
- Nombre del Workflow
- Disparadores: Ejemplo push o pull-request
- Variables de entorno: Variables de configuración que podemos usar en los pasos
- Permisos: Definir a que recursos tiene acceso el workflow
- Jobs: Acciones que queremos automatizar por ejemplo el build
- Ejecutar en: La infraestructura de computo que vamos a usar para realizar la tarea podemos usar gratis contenedores
- Pasos: Tareas que definimos para completar la acción
Cómo se ve este ejemplo en la práctica?




Éste es solo un ejemplo sencillo que espero te ayude a dar el paso de iniciar en el uso de GitHub Actions en tu flujo de trabajo, además invitarte a adoptar practicas DevOps y DevSecOps con el fin de no perder del radar los procesos de calidad y seguridad y ejecutarlos de forma temprana cómo buena práctica.
Gracias por llegar hasta este punto, nos vemos en el próximo post.