Pipeline CI/CD con GitLab para un proyecto Astro con despliegue en Docker

· 15min · gitlab, cicd, docker, astro, devops

Introducción

En este post se describe cómo configurar un pipeline CI/CD completo en GitLab para un proyecto Astro. El pipeline cubre todas las fases: compilación del proyecto, construcción y publicación de la imagen Docker en Docker Hub, y despliegue en un servidor remoto tanto con ficheros estáticos como con Docker Compose.

Estructura del pipeline

El fichero .gitlab-ci.yml define cuatro stages que se ejecutan en orden:

  1. build — compila el proyecto con Node.js
  2. build-image — construye y publica la imagen Docker en Docker Hub
  3. test — reservado para pruebas (no implementado en este ejemplo)
  4. deploy — despliega en el servidor remoto
stages:
  - build
  - build-image
  - test
  - deploy

También se configura caché entre jobs para acelerar las ejecuciones:

cache:
  paths:
    - node_modules
    - package-lock.json
    - dist

Stage 1: Build del proyecto

El job build-job usa la imagen oficial node:lts para instalar dependencias y compilar el proyecto Astro. El artefacto generado (carpeta dist) se pasa al siguiente stage.

build-job:
  stage: build
  image: node:lts
  before_script:
    - npm i
  script:
    - npm run build
  artifacts:
    paths:
      - dist
    expire_in: 1 hour
  only:
    - master

Stage 2: Construcción y publicación de la imagen Docker

El job build-docker-hub usa Docker-in-Docker (DinD) para construir la imagen y subirla a Docker Hub. Se aprovecha el caché de la imagen anterior para acelerar la construcción.

Las variables de entorno necesarias son:

build-docker-hub:
  stage: build-image
  image: docker:latest
  services:
    - name: docker:dind
      alias: docker
  variables:
    DOCKER_HOST: tcp://docker:2375
    DOCKER_TLS_CERTDIR: ''
    DOCKER_DRIVER: overlay2
    IMAGE_TAG: '$CI_PROJECT_NAME:$CI_COMMIT_SHORT_SHA'
    IMAGE_NAME: $CI_PROJECT_NAME
  script:
    - docker pull pepesan/$IMAGE_NAME:latest || true
    - docker build --cache-from pepesan/$IMAGE_NAME:latest -t pepesan/$IMAGE_TAG .
    - docker build --cache-from pepesan/$IMAGE_NAME:latest -t pepesan/$IMAGE_NAME:latest .
    - echo "$DOCKER_HUB_PASSWORD" | docker login -u pepesan --password-stdin
    - docker push pepesan/$IMAGE_NAME:latest
    - docker push pepesan/$IMAGE_TAG
  only:
    - master

Stage 3: Despliegue

Hay dos variantes de despliegue.

Despliegue de ficheros estáticos

El job deploy-job copia la carpeta dist al servidor remoto vía SCP usando una clave SSH almacenada en base64 como variable de GitLab.

deploy-job:
  image: cytopia/ansible:latest-aws
  stage: deploy
  before_script:
    - mkdir -p ~/.ssh
    - eval $(ssh-agent -s)
    - echo "StrictHostKeyChecking no" >> ~/.ssh/config
    - ssh-add <(echo "$SSH_PRIVATE_KEY" | base64 -d)
  script:
    - ssh $SSH_USER@$SSH_HOST "rm -rf $DEPLOY_PATH/*"
    - scp -r dist/* $SSH_USER@$SSH_HOST:$DEPLOY_PATH
    - echo Successfully deployed!
  only:
    - master

Despliegue con Docker Compose

El job deploy-docker-job copia el fichero compose.yaml al servidor y levanta los contenedores con docker compose up -d. Se activa solo desde la rama deploy.

deploy-docker-job:
  image: cytopia/ansible:latest-aws
  stage: deploy
  before_script:
    - mkdir -p ~/.ssh
    - eval $(ssh-agent -s)
    - echo "StrictHostKeyChecking no" >> ~/.ssh/config
    - ssh-add <(echo "$SSH_PRIVATE_KEY_NEW" | base64 -d)
  script:
    - ssh $SSH_USER_NEW@$SSH_HOST_NEW "mkdir -p $DEPLOY_PATH_NEW"
    - ssh $SSH_USER_NEW@$SSH_HOST_NEW "rm -rf $DEPLOY_PATH_NEW/*"
    - scp compose.yaml $SSH_USER_NEW@$SSH_HOST_NEW:$DEPLOY_PATH_NEW
    - ssh $SSH_USER_NEW@$SSH_HOST_NEW "docker compose -f $DEPLOY_PATH_NEW/compose.yaml up -d"
    - echo Successfully deployed!
  only:
    - deploy

Variables de GitLab necesarias

Hay que configurar las siguientes variables en Settings > CI/CD > Variables del proyecto:

VariableTipoDescripción
DOCKER_HUB_PASSWORDVariableContraseña de Docker Hub
SSH_PRIVATE_KEYVariableClave SSH privada en base64 para el servidor de estáticos
SSH_PRIVATE_KEY_NEWVariableClave SSH privada en base64 para el servidor Docker
SSH_USER / SSH_HOSTVariableUsuario y host del servidor de estáticos
SSH_USER_NEW / SSH_HOST_NEWVariableUsuario y host del servidor Docker
DEPLOY_PATH / DEPLOY_PATH_NEWVariableRutas de despliegue en cada servidor

Cómo guardar la clave SSH privada en GitLab

Las claves SSH privadas son ficheros multilínea, y esto causa problemas al intentar guardarlas como variable de tipo Variable en GitLab: la interfaz web puede truncar los saltos de línea y corromper la clave.

Opción recomendada: tipo File

GitLab permite crear variables de tipo File. En este caso GitLab escribe el contenido en un fichero temporal y la variable contiene la ruta a ese fichero, preservando el formato multilínea intacto. El before_script queda así:

before_script:
  - chmod 400 "$SSH_PRIVATE_KEY"
  - eval $(ssh-agent -s)
  - ssh-add "$SSH_PRIVATE_KEY"
  - mkdir -p ~/.ssh
  - echo "StrictHostKeyChecking no" >> ~/.ssh/config

Para crear la variable: en GitLab ve a Settings > CI/CD > Variables, añade una nueva variable, selecciona tipo File y pega directamente el contenido de ~/.ssh/id_rsa (incluyendo las líneas -----BEGIN y -----END).

Opción alternativa: base64 en variable de tipo Variable

Si necesitas una variable de tipo Variable, puedes codificar la clave en base64 para convertirla en una cadena de una sola línea sin espacios ni saltos de línea:

cat ~/.ssh/id_rsa | base64 -w0

Pega el resultado como valor de la variable. En el pipeline hay que decodificarla antes de usarla, que es lo que hace base64 -d en el before_script de este ejemplo. El inconveniente es que las variables con caracteres especiales no pueden enmascararse en GitLab, así que la clave quedará visible en los logs si se imprime accidentalmente.

Conclusión

Con este pipeline CI/CD en GitLab se automatiza completamente el ciclo de vida de un proyecto Astro: desde la compilación hasta el despliegue en producción, con soporte tanto para distribución de ficheros estáticos como para despliegue basado en contenedores Docker. La separación en stages y el uso de variables protegidas de GitLab permite mantener las credenciales seguras y el proceso reproducible.