Automatización DevOps con GitLab CI/CD: Pipelines, Runners y Buenas Prácticas
GitLab CI/CD es una de las herramientas más completas para automatizar el ciclo de vida del software. Con un único archivo .gitlab-ci.yml en la raíz del repositorio puedes definir toda la cadena de integración continua y despliegue continuo, desde las pruebas unitarias hasta el rollout en producción. En este artículo veremos cómo construir pipelines robustos, gestionar variables sensibles y configurar runners para distintos entornos.
¿Qué es GitLab CI/CD?
GitLab CI/CD es el motor de automatización integrado en GitLab que ejecuta pipelines de forma automática ante eventos como push, merge request o tags. A diferencia de herramientas externas como Jenkins, no requiere instalar software adicional si ya usas GitLab.com o una instancia self-hosted: el CI/CD viene incluido y comparte el mismo control de acceso, el mismo registro de contenedores y el mismo sistema de variables.
Un pipeline está compuesto por stages (etapas) y dentro de cada stage hay uno o más jobs (trabajos). Los stages se ejecutan en secuencia; los jobs dentro del mismo stage se ejecutan en paralelo, siempre que haya runners disponibles.
Estructura del archivo .gitlab-ci.yml
Todo comienza en el archivo .gitlab-ci.yml que vive en la raíz del repositorio. La estructura mínima luce así:
stages:
- build
- test
- deploy
variables:
APP_ENV: production
build-job:
stage: build
image: node:20-alpine
script:
- npm ci
- npm run build
artifacts:
paths:
- dist/
expire_in: 1 hour
test-job:
stage: test
image: node:20-alpine
script:
- npm ci
- npm test
coverage: '/Lines\s*:\s*(\d+\.?\d*)%/'
deploy-production:
stage: deploy
script:
- ./scripts/deploy.sh
environment:
name: production
url: https://mi-app.com
only:
- main
La directiva stages define el orden de ejecución. Cada job declara a qué stage pertenece. Si un job no especifica stage, cae en el stage test por defecto.
Variables y Secretos
GitLab ofrece tres niveles para definir variables:
- En el propio .gitlab-ci.yml — para valores no sensibles como nombres de entorno o rutas.
- En Settings → CI/CD → Variables — para secretos como tokens, contraseñas o claves de API. Se pueden marcar como Masked (no se imprimen en logs) y Protected (solo disponibles en ramas protegidas).
- Variables de grupo — definidas a nivel de grupo de GitLab, disponibles para todos los proyectos del grupo.
deploy-job:
stage: deploy
script:
- echo "Desplegando en $APP_ENV"
- curl -X POST "$WEBHOOK_URL" -H "Authorization: Bearer $API_TOKEN"
Las variables definidas en Settings nunca aparecen en el .yml, lo que mantiene limpio el repositorio. Para variables con saltos de línea (como claves PEM), activa la opción Expand variable reference o usa base64 para codificarlas.
GitLab Runners: Tipos y Configuración
Los runners son los agentes que ejecutan los jobs. GitLab ofrece runners compartidos en su SaaS (GitLab.com) y permite registrar runners propios para mayor control. Hay tres tipos principales:
- Shared runners — disponibles para todos los proyectos, mantenidos por GitLab. Ideales para empezar.
- Group runners — disponibles para todos los proyectos de un grupo.
- Specific runners — dedicados a un proyecto en particular. Perfectos para entornos con requisitos especiales (hardware, red privada, etc.).
Para registrar un runner propio en Ubuntu/Debian:
# Instalar el runner
curl -L --output /usr/local/bin/gitlab-runner \
https://gitlab-runner-releases.s3.amazonaws.com/latest/binaries/gitlab-runner-linux-amd64
chmod +x /usr/local/bin/gitlab-runner
# Registrar
gitlab-runner register \
--url https://gitlab.com/ \
--registration-token $TOKEN \
--executor docker \
--docker-image alpine:latest \
--description "Runner produccion" \
--tag-list "produccion,linux"
El ejecutor más recomendado es docker, ya que cada job corre en un contenedor limpio, evitando contaminación entre ejecuciones. Para jobs que necesitan Docker-in-Docker (construir imágenes), usa el servicio docker:dind:
build-image:
stage: build
image: docker:24
services:
- docker:24-dind
variables:
DOCKER_TLS_CERTDIR: "/certs"
script:
- docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA .
- docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
Caché, Artefactos y Dependencias
Dos mecanismos reducen drásticamente los tiempos de pipeline:
- cache — persiste directorios entre ejecuciones del mismo runner. Ideal para
node_modules,.venvo la caché de Maven. - artifacts — pasa archivos de un stage al siguiente dentro del mismo pipeline. También los hace descargables desde la UI.
build-job:
stage: build
cache:
key: "$CI_COMMIT_REF_SLUG"
paths:
- node_modules/
script:
- npm ci
- npm run build
artifacts:
paths:
- dist/
reports:
junit: reports/junit.xml
expire_in: 7 days
La clave $CI_COMMIT_REF_SLUG crea una caché por rama, evitando colisiones entre main y feature branches.
Entornos y Despliegues Controlados
GitLab permite modelar entornos (staging, production) con la directiva environment. Esto habilita el panel Deployments en la UI, historial de despliegues y la opción de hacer rollback con un clic.
deploy-staging:
stage: deploy
script:
- ssh deployer@$STAGING_HOST "cd /app && git pull && ./restart.sh"
environment:
name: staging
url: https://staging.mi-app.com
only:
- develop
deploy-production:
stage: deploy
script:
- ssh deployer@$PROD_HOST "cd /app && git pull && ./restart.sh"
environment:
name: production
url: https://mi-app.com
when: manual
only:
- main
Nota la directiva when: manual en el despliegue a producción: el job queda pendiente y requiere aprobación desde la UI antes de ejecutarse. Esto añade una barrera humana justo donde más se necesita.
Pipelines Avanzados: Includes, Reglas y DAG
Cuando el proyecto crece, conviene dividir el .gitlab-ci.yml en múltiples archivos con include:
include:
- local: '.gitlab/ci/build.yml'
- local: '.gitlab/ci/test.yml'
- project: 'mi-grupo/templates-ci'
ref: main
file: '/templates/deploy.yml'
Las rules reemplazan a only/except y ofrecen lógica condicional más expresiva:
test-job:
rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
when: always
- if: '$CI_COMMIT_BRANCH == "main"'
when: always
- when: never
Para pipelines con dependencias no lineales entre jobs, GitLab soporta DAG (Directed Acyclic Graph) con la directiva needs:
deploy-api:
stage: deploy
needs: ["build-api", "test-api"]
script: ./deploy_api.sh
deploy-frontend:
stage: deploy
needs: ["build-frontend"]
script: ./deploy_frontend.sh
Buenas Prácticas para Pipelines Eficientes
- Usa imágenes específicas — evita
:latest; fija la versión para builds reproducibles (node:20.12-alpine). - Minimiza el tiempo de pipeline — paraleliza con jobs en el mismo stage, usa caché y DAG para evitar esperas innecesarias.
- Separa las responsabilidades — un job, una responsabilidad. Evita scripts enormes; usa scripts externos versionados.
- Protege las ramas y variables — activa Protected branches para
mainy marca las variables sensibles como Protected + Masked. - Notifica el resultado — integra Slack o email para alertar fallos en main. GitLab tiene integraciones nativas en Settings → Integrations.
- Revisa los tiempos regularmente — la sección CI/CD Analytics muestra histograma de duración por pipeline. Un pipeline que supera los 15 minutos suele tener optimizaciones pendientes.
Conclusión
GitLab CI/CD es un ecosistema completo que crece con las necesidades del equipo. Empezar es simple: un archivo YAML, un runner, y ya tienes automatización. Pero las capacidades avanzadas (DAG, environments, includes, reglas condicionales) permiten construir pipelines de nivel enterprise sin salir de la plataforma. La clave está en empezar simple, medir los tiempos y refactorizar el pipeline igual que refactorizas el código: con disciplina y versión controlada.