Appearance
Pipelines CI/CD
El despliegue se realiza en dos niveles de pipelines de GitLab:
- Pipeline de cada repositorio de servicio: construye la imagen Docker, la publica en el registry y dispara el orquestador.
- Pipeline del orquestador (
infra-deploy): se conecta por SSH al host del entorno y actualiza los contenedores.
| Rama | Entorno | Tag latest | Variables usadas |
|---|---|---|---|
develop | Test | develop-latest | DEV_* |
main | Producción | main-latest | PROD_* |
Todos los jobs se ejecutan en runners con el tag test (excepto el build de Windows de Custodio, que usa el tag windows).
Pipeline de los repositorios de servicio
Aplica a todos los backends, frontends y manuales.
Stages
| Stage | Job | Imagen del job |
|---|---|---|
deploy | build | docker:26 + servicio docker:26-dind |
trigger | trigger-infra-deploy | curlimages/curl:8.8.0 |
Job build
Se ejecuta en develop y main:
yaml
build:
stage: deploy
image: docker:26
services:
- docker:26-dind
rules:
- if: '$CI_COMMIT_BRANCH == "develop"'
- if: '$CI_COMMIT_BRANCH == "main"'
before_script:
- docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" $CI_REGISTRY
script:
- export TAG="${CI_COMMIT_REF_SLUG}-${CI_COMMIT_SHORT_SHA}"
- docker build -t "$IMAGE:$TAG" -t "$IMAGE:${CI_COMMIT_REF_SLUG}-latest" .
- docker push "$IMAGE:$TAG"
- docker push "$IMAGE:${CI_COMMIT_REF_SLUG}-latest"Cada build publica dos tags:
| Tag | Ejemplo | Uso |
|---|---|---|
<rama>-<sha corto> | main-a1b2c3d4 | Versión inmutable; permite rollback |
<rama>-latest | main-latest | Tag que usa docker-compose.yml en el host |
Frontends: variables por rama
En los frontends, cada regla asigna las variables VITE_* desde su versión DEV_ o PROD_ y se pasan como --build-arg:
yaml
rules:
- if: '$CI_COMMIT_BRANCH == "develop"'
variables:
VITE_TITLE_APP: $DEV_VITE_TITLE_APP
VITE_API_ENDPOINT: $DEV_VITE_API_ENDPOINT
# ...
- if: '$CI_COMMIT_BRANCH == "main"'
variables:
VITE_TITLE_APP: $PROD_VITE_TITLE_APP
VITE_API_ENDPOINT: $PROD_VITE_API_ENDPOINT
# ...
script:
- |
docker build \
--build-arg VITE_TITLE_APP="${VITE_TITLE_APP}" \
--build-arg VITE_API_ENDPOINT="${VITE_API_ENDPOINT}" \
# ...
-t "$IMAGE:$TAG" -t "$IMAGE:${CI_COMMIT_REF_SLUG}-latest" .La lista de build args de cada frontend está en Imágenes Docker.
Job trigger-infra-deploy
Se ejecuta después de build (needs: build) y dispara el pipeline de infra-deploy en la misma rama, indicando qué servicio desplegar:
yaml
trigger-infra-deploy:
stage: trigger
image: curlimages/curl:8.8.0
rules:
- if: '$CI_COMMIT_BRANCH == "develop"'
- if: '$CI_COMMIT_BRANCH == "main"'
needs:
- job: build
script:
- |
curl -fsSk --retry 3 -X POST \
--form token=$INFRA_DEPLOY_TRIGGER_TOKEN \
--form ref=$CI_COMMIT_BRANCH \
--form "variables[SERVICE_NAME]=<nombre-del-servicio>" \
"https://git.eje.gob.bo/api/v4/projects/13/trigger/pipeline"refdetermina el entorno:develop→ test,main→ producción.SERVICE_NAMEdebe coincidir con el nombre del servicio en eldocker-compose.yml(igual al nombre del repositorio).
Pipeline orquestador (infra-deploy)
Un único job, deploy-orquestador (stage deploy, imagen alpine:3.19), que se ejecuta en develop y main. Según la rama, mapea las variables DEV_* o PROD_* a nombres genéricos:
yaml
rules:
- if: '$CI_COMMIT_BRANCH == "develop"'
variables:
DEPLOY_USER: $DEV_DEPLOY_USER
DEPLOY_HOST: $DEV_DEPLOY_HOST
SSH_PRIVATE_KEY: $DEV_SSH_PRIVATE_KEY
IMAGE_TAG: develop-latest
IDP_DEPLOY_PATH: $DEV_DEPLOY_BASE_PATH/idp
SELECCIONADOR_DEPLOY_PATH: $DEV_DEPLOY_BASE_PATH/seleccionador
DOCS_DEPLOY_PATH: $DEV_DEPLOY_BASE_PATH/docs
# ... BACK_ENV y secretos de cada backend
- if: '$CI_COMMIT_BRANCH == "main"'
variables:
# ... mismas variables con prefijo PROD_ e IMAGE_TAG: main-latestPasos del despliegue
Conexión SSH: instala
openssh-client, carga la clave enssh-agenty registra el host enknown_hostsconssh-keyscan.Directorios: crea en el host
storage/publicystorage/privatede IDP y Seleccionador, y el directorio de docs.Archivos de configuración: copia por
scp:.envconIMAGE_TAG=<rama>-latesta los tres stacks.idp/,seleccionador/ydocs/docker-compose.yml.- El archivo de variables de cada backend como
back.env.tmp.
Composición de
back.env(en el host): por cada backend concatenaback.env.tmpcon los secretos individuales y elimina el temporal. Se regenera completo en cada ejecución:shcat back.env.tmp > back.env echo "SECRET_KEY=${IDP_SECRET_KEY}" >> back.env echo "SIGNING_KEY_MASTER_SECRET=${IDP_SIGNING_KEY_MASTER_SECRET}" >> back.env echo "DATABASE_PASS=${IDP_DATABASE_PASS}" >> back.env rm back.env.tmpLogin al registry con el deploy token del grupo (
REGISTRY_DEPLOY_TOKEN_USER/REGISTRY_DEPLOY_TOKEN_PASSWORD).Despliegue:
Con
SERVICE_NAME(disparado por un repositorio): ubica el stack al que pertenece el servicio y ejecuta solo para él:shdocker compose pull "$TARGET_SERVICE" docker compose up -d --no-deps "$TARGET_SERVICE"Si el valor no es un servicio conocido, el job falla.
Sin
SERVICE_NAME(ejecución manual del pipeline): hacepullyup -d --no-depsde todos los servicios de los tres stacks.
Verificación: espera hasta 120 s (compartidos entre los tres stacks) a que todos los contenedores estén
runningy, si tienen health check,healthy. Si un backend no terminó de iniciar, imprime sus últimas 200 líneas de log.Resumen: imprime una tabla con proyecto, servicio, estado, salud y puertos publicados.
TIP
El paso 7 no hace fallar el job: si un servicio no queda saludable, el pipeline termina igual pero muestra el aviso y los logs. Revise siempre la salida del job tras un despliegue.
Valores válidos de SERVICE_NAME
| Stack | Servicios |
|---|---|
idp | auth-backend, auth-authorization-frontend, auth-frontend-admin, frontend-public-auth |
seleccionador | backend-seleccion-postulantes, frontend-portal-admin-seleccion-postulantes, frontend-portal-comisiones-seleccion-postulantes, frontend-portal-postulantes-seleccion-postulantes, frontend-estacion-examen |
docs | frontend-manual-tecnico, frontend-manual-de-usuario |
Ejecución manual
Despliegue completo de un entorno (todos los servicios), desde una máquina con acceso a GitLab:
sh
curl -fsSk --retry 3 -X POST \
--form token=$INFRA_DEPLOY_TRIGGER_TOKEN \
--form ref=develop \
"https://git.eje.gob.bo/api/v4/projects/13/trigger/pipeline"Despliegue de un solo servicio:
sh
curl -fsSk --retry 3 -X POST \
--form token=$INFRA_DEPLOY_TRIGGER_TOKEN \
--form ref=main \
--form "variables[SERVICE_NAME]=auth-backend" \
"https://git.eje.gob.bo/api/v4/projects/13/trigger/pipeline"También puede ejecutarse desde GitLab: Build → Pipelines → Run pipeline en infra-deploy, eligiendo la rama y, opcionalmente, la variable SERVICE_NAME.
Agregar un nuevo servicio
- Crear el
Dockerfile(conEXPOSE 80y endpoint/health/) en el repositorio del servicio. - Agregar los jobs
buildytrigger-infra-deploycon suSERVICE_NAME. - Agregar el servicio al
docker-compose.ymldel stack correspondiente eninfra-deploy, con un puerto libre. - Agregar el nombre en el
casedel despliegue selectivo y en el bloque de despliegue completo de.gitlab-ci.ymldeinfra-deploy. - Actualizar el mapa de subdominios y puertos.
Custodio
custodio tiene un pipeline independiente que no despliega en servidores: genera ejecutables portables y los publica en el Package Registry de GitLab. Se dispara al crear un tag Git.
| Stage | Job | Runner | Descripción |
|---|---|---|---|
test | test | test | pytest tests/ sobre la lógica de negocio (python:3.12-slim) |
test | test:gui-import | test | Smoke test: inicializa la GUI Tkinter bajo Xvfb |
build | build:linux | test | PyInstaller con Custodio.spec en debian:bookworm-slim → Custodio-linux-<tag> |
build | build:windows | windows | PyInstaller en runner Windows nativo → Custodio-windows-<tag>.exe |
verify | checksums | test | Genera CHECKSUMS.txt (SHA-256 de ambos ejecutables) |
publish | publish | test | Manual. Sube ejecutables y checksums a packages/generic/custodio/<tag>/ |
- Los jobs de
testcorren automáticamente con tags; en merge requests y enmainquedan como manuales. - Los artefactos se conservan 6 meses.
publishes un paso manual deliberado para controlar qué versión se distribuye.
