Skip to content

Pipelines CI/CD ​

El despliegue se realiza en dos niveles de pipelines de GitLab:

  1. Pipeline de cada repositorio de servicio: construye la imagen Docker, la publica en el registry y dispara el orquestador.
  2. Pipeline del orquestador (infra-deploy): se conecta por SSH al host del entorno y actualiza los contenedores.
RamaEntornoTag latestVariables usadas
developTestdevelop-latestDEV_*
mainProducciónmain-latestPROD_*

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 ​

StageJobImagen del job
deploybuilddocker:26 + servicio docker:26-dind
triggertrigger-infra-deploycurlimages/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:

TagEjemploUso
<rama>-<sha corto>main-a1b2c3d4Versión inmutable; permite rollback
<rama>-latestmain-latestTag 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"
  • ref determina el entorno: develop → test, main → producción.
  • SERVICE_NAME debe coincidir con el nombre del servicio en el docker-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-latest

Pasos del despliegue ​

  1. Conexión SSH: instala openssh-client, carga la clave en ssh-agent y registra el host en known_hosts con ssh-keyscan.

  2. Directorios: crea en el host storage/public y storage/private de IDP y Seleccionador, y el directorio de docs.

  3. Archivos de configuración: copia por scp:

    • .env con IMAGE_TAG=<rama>-latest a los tres stacks.
    • idp/, seleccionador/ y docs/docker-compose.yml.
    • El archivo de variables de cada backend como back.env.tmp.
  4. Composición de back.env (en el host): por cada backend concatena back.env.tmp con los secretos individuales y elimina el temporal. Se regenera completo en cada ejecución:

    sh
    cat 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.tmp
  5. Login al registry con el deploy token del grupo (REGISTRY_DEPLOY_TOKEN_USER / REGISTRY_DEPLOY_TOKEN_PASSWORD).

  6. Despliegue:

    • Con SERVICE_NAME (disparado por un repositorio): ubica el stack al que pertenece el servicio y ejecuta solo para él:

      sh
      docker 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): hace pull y up -d --no-deps de todos los servicios de los tres stacks.

  7. Verificación: espera hasta 120 s (compartidos entre los tres stacks) a que todos los contenedores estén running y, si tienen health check, healthy. Si un backend no terminó de iniciar, imprime sus últimas 200 líneas de log.

  8. 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 ​

StackServicios
idpauth-backend, auth-authorization-frontend, auth-frontend-admin, frontend-public-auth
seleccionadorbackend-seleccion-postulantes, frontend-portal-admin-seleccion-postulantes, frontend-portal-comisiones-seleccion-postulantes, frontend-portal-postulantes-seleccion-postulantes, frontend-estacion-examen
docsfrontend-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 ​

  1. Crear el Dockerfile (con EXPOSE 80 y endpoint /health/) en el repositorio del servicio.
  2. Agregar los jobs build y trigger-infra-deploy con su SERVICE_NAME.
  3. Agregar el servicio al docker-compose.yml del stack correspondiente en infra-deploy, con un puerto libre.
  4. Agregar el nombre en el case del despliegue selectivo y en el bloque de despliegue completo de .gitlab-ci.yml de infra-deploy.
  5. 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.

StageJobRunnerDescripción
testtesttestpytest tests/ sobre la lógica de negocio (python:3.12-slim)
testtest:gui-importtestSmoke test: inicializa la GUI Tkinter bajo Xvfb
buildbuild:linuxtestPyInstaller con Custodio.spec en debian:bookworm-slim → Custodio-linux-<tag>
buildbuild:windowswindowsPyInstaller en runner Windows nativo → Custodio-windows-<tag>.exe
verifychecksumstestGenera CHECKSUMS.txt (SHA-256 de ambos ejecutables)
publishpublishtestManual. Sube ejecutables y checksums a packages/generic/custodio/<tag>/
  • Los jobs de test corren automáticamente con tags; en merge requests y en main quedan como manuales.
  • Los artefactos se conservan 6 meses.
  • publish es un paso manual deliberado para controlar qué versión se distribuye.