Tabla de Contenidos
7. Despliegue en el proyecto
Objetivo del tema: /git-flow publish = producción actualizada, sin entrar nunca al servidor. Y en cada push, GitHub compila y pasa todos los tests. develop no se despliega en ningún sitio: es local.
Lo que ya sabéis: qué es GitHub Actions y un primer workflow con VPN y SSH, en Despliegue 5; cómo funciona nginx-proxy con VIRTUAL_HOST, en Despliegue 4.c. Lo nuevo aquí es sustituir la VPN y el SSH por un runner dentro del servidor.
El servidor
Un VPS compartido por toda la clase, con tres cosas montadas por el profesor:
- Docker.
- nginx-proxy en la red
nginx-proxy-network, escuchando en el 80. Cada aplicación se publica sola por suVIRTUAL_HOST:<alumno>.proyectosdaw.cip.fpmislata.com, que ya está encompose.yamldesde el tema 4. - Un runner autoalojado de GitHub por alumno, registrado contra su repositorio. Un runner es un proceso que espera trabajos de GitHub Actions y los ejecuta en la máquina donde está instalado. Como está en el servidor, un workflow que se ejecute en él puede hacer
docker composedirectamente: ni SSH, ni VPN, ni registro de imágenes. El profesor lo registra con el token que da GitHub en Settings → Actions → Runners → New self-hosted runner; es la única vez que alguien entra al servidor.
docker ps cada uno ve los contenedores de los demás. Es un servidor de clase y se asume. Lo que separa a cada alumno es el nombre de proyecto de Compose (el del repositorio) y su subdominio.
Los mismos scripts
En el servidor no hay nada nuevo que aprender: la aplicación se opera con el mismo script del tema 4, app.sh, con sus cuatro subcomandos. Lo único que cambia es una variable de entorno que fija el workflow:
COMPOSE_FILE=compose.yaml
- Con
COMPOSE_FILE=compose.yamlCompose carga solo producción: elcompose.override.yamlde desarrollo no se aplica, así que ning serve, ni puertos publicados, ni datos de prueba. app.sh starthaceup -d –build –wait: construye la imagen con el código del commit, sustituye los contenedores que han cambiado y espera a loshealthcheck. Nunca hacedown -v: el volumendb-datase queda y un despliegue nunca borra la base de datos; Flyway aplica las migraciones nuevas al arrancar.- Los workflows solo llaman a
app.sh, así que lo mismo que hace GitHub se puede lanzar a mano desde el servidor si un día hace falta.
Secretos
La contraseña de la base de datos de producción no está en ningún fichero: está en Settings → Secrets and variables → Actions del repositorio como DB_PASSWORD. El workflow la pasa como variable de entorno a app.sh y Compose la lee en ${DB_PASSWORD}. En desarrollo esa misma variable viene del .env del worktree.
Los workflows
Un solo fichero para CI y CD:
name: CI/CD
on:
push:
branches: [develop, main]
workflow_dispatch:
jobs:
ci:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v4
with: { distribution: temurin, java-version: 25 }
- uses: actions/setup-node@v4
with: { node-version: 22 }
- name: Levantar la aplicación
env: { DB_PASSWORD: ci }
run: |
docker network create nginx-proxy-network
docker compose up -d --build --wait
- name: Tests del backend (unitarios y ArchUnit)
working-directory: backend
run: mvn -B test
- name: Tests de API y E2E
working-directory: frontend
run: |
npm ci
npx playwright install --with-deps chromium
npm run test:api
npm run test:e2e
deploy:
needs: ci
if: github.ref == 'refs/heads/main'
runs-on: self-hosted
steps:
- uses: actions/checkout@v4
- name: Desplegar
env:
COMPOSE_FILE: compose.yaml
DB_PASSWORD: ${{ secrets.DB_PASSWORD }}
run: ./app.sh start
- name: Smoke test
run: curl -fsS http://<alumno>.proyectosdaw.cip.fpmislata.com/api/clientes > /dev/null
Lo que hay que entender:
cicorre en un runner de GitHub (ubuntu-latest), en cada push adevelopy amain. Levanta la aplicación con el mismocompose.yaml+compose.override.yamldel portátil (sin.env: valen los puertos por defecto y el perfildatos-prueba), y pasa los tests con los mismos comandos que en el worktree:mvn test,npm run test:apiynpm run test:e2e.–waitespera a loshealthcheck.deploycorre en el runner del servidor (self-hosted), solo enmainy solo siciha pasado (needs). Hacecheckoutdel commit y./app.sh startconCOMPOSE_FILE=compose.yaml: la imagen se construye en el servidor con ese código.- El smoke test es el último paso: una petición a la URL de producción que tiene que responder 200. Si el despliegue ha dejado la aplicación caída, el workflow sale en rojo y se ve en el acto.
workflow_dispatchpermite relanzarlo a mano desde la pestaña Actions (siempre sobremain).
Y dos workflows pequeños de operación, status.yml y logs.yml, solo con workflow_dispatch y un paso ./app.sh status o ./app.sh logs (con el mismo COMPOSE_FILE) en self-hosted. Son la forma de ver qué pasa en producción sin acceso al servidor: se lanzan desde Actions y la salida queda en el log del workflow.
La IA y los workflows
El YAML lo escribe el agente a partir de lo de arriba. Y no hay skill para cuando CI sale en rojo, porque no debería salir en rojo: pasa los mismos comandos que /git-flow finish acaba de pasar en el worktree. Si sale, es que algo distingue el runner del portátil (un fichero que no está en git, una variable, un puerto) o que ha fallado el despliegue o el smoke test. Un workflow rojo tiene un log largo y la causa está en tres líneas: se abre el paso fallido en Actions, se copia su log y se le pregunta al agente en una conversación nueva. Desde la terminal, gh run view –log-failed da solo los pasos que han fallado, que es lo que hay que pegarle (tema 5: no pegarle logs enteros).
Ejercicios
Ejercicio 1
- Crea un issue «CI» e inícialo.
- Comprueba que
app.shfunciona como lo va a ejecutar el servidor, sin.envy solo con producción:COMPOSE_FILE=compose.yaml ./app.sh startdesde el repositorio principal,./app.sh statusy luego./app.sh stop. - Añade el secreto
DB_PASSWORDen Settings → Secrets and variables → Actions. - Escribe
.github/workflows/ci-cd.ymlsolo con el jobci. Finaliza el issue y mira en Actions cómo se ejecuta en el push adevelop. Si sale en rojo, arréglalo hasta que esté en verde.
Ejercicio 2
- Pide al profesor que registre tu runner en el servidor. Comprueba en Settings → Actions → Runners que aparece como Idle.
- Crea un issue «Despliegue» e inícialo. Añade el job
deployal workflow con elneeds: ci, elifsobremainy el smoke test contra tu URL. - Finaliza el issue y
/git-flow publish. Sigue la ejecución en Actions y comprueba que la aplicación responde enhttp://<alumno>.proyectosdaw.cip.fpmislata.com.
Ejercicio 3
- Añade
status.ymlylogs.ymlconworkflow_dispatchsobreself-hosted. - Lánzalos desde Actions y lee la salida de
app.sh statusen el log.
Ejercicio 4
- Cambia a propósito la URL del smoke test por una que no existe, finaliza el issue y publica. CI pasa y
deploysale en rojo. Copia el log del paso fallido, pregúntaselo al agente y comprueba que señala la URL. Arréglala y vuelve a publicar. - Haz un cambio visible en la aplicación, finaliza y publica. Comprueba en producción que el cambio está y que los datos que había siguen estando.
Fuera de este tema, por ser de un sistema real y no de un proyecto de clase: entornos de dev y pre, copias de seguridad, copiar datos de producción, tareas programadas y HTTPS (el proxy sirve HTTP).
