Herramientas de usuario

Herramientas del sitio


clase:daw:daw-proyecto:tema03

¡Esta es una revisión vieja del documento!


3. Docker en el proyecto

Objetivo del tema: cada worktree tiene su propia aplicación completa funcionando (base de datos, backend y frontend), sin chocar con las de los demás worktrees. Al acabar, /git-flow iniciar deja la aplicación del issue levantada y con datos.

Lo que ya sabéis y aquí no se repite: imágenes y contenedores (Despliegue 3), Dockerfile (Despliegue 4), Docker Compose (Despliegue 4.b) y redes y nginx-proxy (Despliegue 4.c). Automatizar Maven y Node: Proyecto 3.

Tres contenedores

La aplicación son tres contenedores, uno por caja del diagrama C4 de contenedores:

Servicio Imagen Qué hace
db mariadb La base de datos. Solo accesible desde el backend
backend Construida: Maven → JRE El JAR de Spring Boot en el puerto 8080
frontend Construida: Angular → Nginx Sirve el Angular compilado y hace de proxy de /api al backend

El navegador solo habla con frontend. Todo lo que empieza por /api lo reenvía Nginx al backend, así el frontend no necesita saber en qué puerto está el backend y no hay problemas de CORS.

Los dos Dockerfile son multi-stage y los escribe la IA. Lo único que hay que vigilar:

FROM maven:3.9-eclipse-temurin-25 AS build
WORKDIR /app
COPY pom.xml .
RUN --mount=type=cache,target=/root/.m2 mvn -q dependency:go-offline
COPY src ./src
RUN --mount=type=cache,target=/root/.m2 mvn -q -DskipTests package

FROM eclipse-temurin:25-jre
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*
COPY --from=build /app/target/*.jar /app.jar
ENTRYPOINT ["java", "-jar", "/app.jar"]

El curl es para el healthcheck de abajo. El –mount=type=cache sobre .m2 es lo que hace que reconstruir la imagen tarde segundos y no minutos: sin él, cada build vuelve a descargar todas las dependencias.

FROM node:22 AS build
WORKDIR /app
COPY package*.json .
RUN npm ci
COPY . .
RUN npx ng build --configuration production

FROM nginx:alpine
COPY nginx.conf /etc/nginx/conf.d/default.conf
COPY --from=build /app/dist/frontend/browser /usr/share/nginx/html

server {
    listen 80;
    root /usr/share/nginx/html;

    location /api/ {
        proxy_pass http://backend:8080/api/;
    }

    location / {
        try_files $uri $uri/ /index.html;
    }
}

El try_files es obligatorio en una aplicación Angular: sin él, recargar el navegador en /clientes/12 da un 404 de Nginx.

Producción y desarrollo con los mismos ficheros

Docker Compose carga compose.yaml y, si existe, compose.override.yaml encima. Eso se usa así:

  • compose.yaml es producción: imágenes construidas, la base de datos en una red interna sin puertos publicados, el frontend en la red de nginx-proxy con su VIRTUAL_HOST.
  • compose.override.yaml cambia lo de desarrollo. Está en el repositorio y Compose lo carga solo. En el servidor, manage.sh (tema 6) usa -f compose.yaml y el override no se aplica.

services:
  db:
    image: mariadb:11
    environment:
      MARIADB_DATABASE: proyecto
      MARIADB_USER: proyecto
      MARIADB_PASSWORD: ${DB_PASSWORD}
      MARIADB_ROOT_PASSWORD: ${DB_PASSWORD}
    volumes:
      - db-data:/var/lib/mysql
    networks:
      - interna
    healthcheck:
      test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
      interval: 5s
      retries: 20

  backend:
    build: ./backend
    environment:
      SPRING_DATASOURCE_URL: jdbc:mariadb://db:3306/proyecto
      SPRING_DATASOURCE_USERNAME: proyecto
      SPRING_DATASOURCE_PASSWORD: ${DB_PASSWORD}
    depends_on:
      db:
        condition: service_healthy
    networks:
      - interna
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8080/api/hola"]
      interval: 5s
      retries: 20

  frontend:
    build: ./frontend
    environment:
      VIRTUAL_HOST: lorenzo.proyectosdaw.cip.fpmislata.com
    depends_on:
      backend:
        condition: service_healthy
    networks:
      - interna
      - proxy

volumes:
  db-data:

networks:
  interna:
  proxy:
    external: true
    name: nginx-proxy-network

Los healthcheck con depends_on: condition: service_healthy hacen que el backend no arranque hasta que MariaDB acepte conexiones, y el frontend hasta que el backend responda. Sin eso, el backend muere al arrancar porque la base de datos aún no está lista.

services:
  db:
    ports:
      - "${DB_PORT:-3306}:3306"

  backend:
    ports:
      - "${BACKEND_PORT:-8080}:8080"
    environment:
      SPRING_PROFILES_ACTIVE: ${PERFIL:-datos-prueba}

  frontend:
    build:
      target: build
    command: npx ng serve --host 0.0.0.0 --proxy-config proxy.conf.json
    volumes:
      - ./frontend:/app
      - /app/node_modules
    ports:
      - "${FRONTEND_PORT:-4200}:4200"

Lo que cambia en desarrollo:

  • Se publican los puertos, para entrar desde el navegador y para que Maven y Playwright lleguen a la base de datos y al frontend desde el host.
  • El frontend pasa a ng serve con la carpeta local frontend/ montada dentro: al guardar un fichero, el navegador se recarga. proxy.conf.json manda /api a http://backend:8080. El volumen anónimo sobre node_modules evita que los módulos instalados en la imagen se tapen con los del host.
  • El backend no tiene recarga en caliente: cada cambio en Java es docker compose up –build backend. Con la caché de Maven son unos segundos. Y no pasa nada.
  • Datos de prueba activados (abajo).
La red nginx-proxy-network es externa: en el servidor la crea nginx-proxy, pero en el portátil hay que crearla una vez, docker network create nginx-proxy-network, o Compose se niega a arrancar. /git-flow iniciar lo hace si no existe.

Un compose por worktree

Dos worktrees con el mismo compose.yaml crearían contenedores con el mismo nombre y pedirían los mismos puertos. Lo resuelve un fichero .env en cada worktree, ignorado por git, que Compose lee solo:

COMPOSE_PROJECT_NAME=mi-proyecto-12-alta-clientes
DB_PORT=33112
BACKEND_PORT=8112
FRONTEND_PORT=4212
DB_PASSWORD=dev

  • COMPOSE_PROJECT_NAME es el prefijo de contenedores, redes y volúmenes de ese worktree. Dos proyectos distintos no se tocan.
  • Los puertos los busca /git-flow iniciar al crear el worktree: prueba a partir de una base y se queda con los libres.
  • Las variables llevan valor por defecto, ${BACKEND_PORT:-8080}, para cuando no hay .env: en CI (tema 6).

/git-flow finalizar termina con docker compose down -v: se van los contenedores y el volumen de la base de datos de ese worktree. No queda nada.

La primera tabla

Hasta ahora el backend no tiene base de datos. Se añaden ahora, con un prompt directo al agente (el flujo de trabajo de verdad llega en el tema 4; esto es solo para tener algo que ver):

  • Dependencias spring-boot-starter-data-jpa, mariadb-java-client y flyway-mysql.
  • Una migración backend/src/main/resources/db/migration/V1__clientes.sql con una tabla.
  • Un GET /api/clientes que la lista y una página de Angular que la muestra.

El esquema siempre va en migraciones de Flyway, V<n>__descripcion.sql, y nunca con ddl-auto. Así cada worktree, cada runner de CI y el servidor construyen la misma base de datos desde cero.

Datos de prueba

Cada worktree arranca con la base de datos vacía: no hay nada que ver en la aplicación ni se pueden escribir tests E2E que dependan de que exista un cliente. Los datos de prueba se separan del esquema:

backend/src/main/resources/db/
├── migration/            <- esquema: V1__clientes.sql, V2__pedidos.sql ...
└── datos-prueba/         <- datos: R__clientes.sql, R__pedidos.sql ...

Los de datos-prueba/ son migraciones repetibles de Flyway (R__): se ejecutan después de todas las versionadas, y como la base de datos de un worktree siempre nace vacía, se ejecutan una vez. Flyway solo mira esa carpeta cuando está activo el perfil datos-prueba:

spring.flyway.locations=classpath:db/migration,classpath:db/datos-prueba

  • En desarrollo el perfil lo activa compose.override.yaml. Para probar la aplicación vacía: PERFIL=default docker compose up -d –build backend.
  • En producción compose.yaml no activa el perfil: los datos de prueba nunca llegan al servidor.
  • Para volver al estado inicial (antes de pasar los E2E, por ejemplo): docker compose down -v && docker compose up -d.

Cada entidad nueva trae su R__<entidad>.sql con unos cuantos registros; la skill /crud (tema 5) lo genera.

Dónde corren Maven y los tests

Las imágenes son de ejecución: el backend es un JRE sin Maven y el frontend es Nginx. Así que compilar y pasar tests se hace en el host, que para eso están instalados el JDK, Maven y Node:

Qué Dónde Contra qué
mvn test (unitarios y ArchUnit) backend/ del worktree Nada: no necesitan la aplicación levantada
mvn verify -Papi -DDB_PORT=<puerto> (API) backend/ del worktree La base de datos del worktree, por ese puerto
npm run e2e (E2E) frontend/ del worktree http://localhost:$FRONTEND_PORT

Son los tres comandos de tests del proyecto y no hay scripts .sh ni .bat: Maven y npm funcionan igual en Linux, Windows y Mac, y son lo que ejecutan el alumno, el agente y CI (tema 6). mvn test forma parte del build y corre en cada compilación. Los de API y los E2E no forman parte del build: se lanzan aparte, cuando se quiere, con los otros dos comandos (tema 5).

npm run

npm run <nombre> ejecuta un comando guardado en la sección scripts del package.json. Es la forma de tener en el proyecto los comandos largos con un nombre corto, sin depender del sistema operativo: npm los ejecuta desde la carpeta del package.json y con los programas de node_modules/.bin en el PATH, así que playwright se escribe sin npx.

"scripts": {
  "start": "ng serve",
  "build": "ng build",
  "e2e": "docker compose down -v && docker compose up -d --wait && playwright test"
}

npm run e2e resetea la base de datos del worktree, espera a que la aplicación esté sana y pasa los tests E2E. El && lo entienden igual cmd y bash. npm run sin nombre lista los scripts que hay. start y build ya los trae Angular: son los que ejecutan npm start y npm run build.

Para que los tests de API encuentren la base de datos del worktree, application.properties lee el puerto con un valor por defecto: spring.datasource.url=jdbc:mariadb://localhost:${DB_PORT:3306}/proyecto. Spring lo toma tanto de una variable de entorno como de una propiedad de Maven, y la propiedad (-DDB_PORT=33112) funciona igual en Windows y en Linux, así que es la que se usa. El valor está en el .env del worktree; el agente lo lee de ahí.

Esto va en AGENTS.md tal cual, porque es lo que el agente ejecuta para comprobar su trabajo. Si no lo sabe, intentará mvn test dentro del contenedor y fallará, o ni probará.

Ejercicios

Ejercicio 1

  • Crea un issue «Aplicación en Docker» e inícialo con /git-flow.
  • Pide al agente los dos Dockerfile, compose.yaml, compose.override.yaml, frontend/nginx.conf y frontend/proxy.conf.json siguiendo este tema. Revisa que el backend tiene la caché de .m2, que db no publica puertos en compose.yaml y que nginx.conf tiene el try_files.
  • Añade .env al .gitignore y crea uno a mano en el worktree con COMPOSE_PROJECT_NAME, los tres puertos y DB_PASSWORD.
  • docker network create nginx-proxy-network y docker compose up -d –build. Comprueba con docker compose ps que los tres servicios están healthy.
  • Entra en http://localhost:<FRONTEND_PORT> y comprueba que se ve el /api/hola.

Ejercicio 2

  • En el mismo worktree, pide al agente que añada JPA, MariaDB y Flyway, la migración V1__clientes.sql, un GET /api/clientes y una página de Angular que liste los clientes.
  • Crea application-datos-prueba.properties y db/datos-prueba/R__clientes.sql con cinco clientes.
  • docker compose up -d –build backend y comprueba que la lista muestra los cinco clientes.
  • Arranca con PERFIL=default tras un down -v y comprueba que la lista sale vacía. Vuelve a dejarlo con datos.
  • Cambia un texto de la página de Angular, guarda y comprueba que el navegador se recarga solo.

Ejercicio 3

  • Añade a application.properties la URL con ${DB_PORT:3306} y ejecuta mvn test desde backend/. Tiene que pasar sin nada levantado.
  • Escribe en AGENTS.md: cómo se levanta la aplicación, que los tests se lanzan en el host con mvn test, mvn verify -Papi -DDB_PORT=<el del .env> y npm run e2e, y que el esquema va siempre en migraciones.
  • /git-flow commit y /git-flow finalizar.

Ejercicio 4

  • Escribe la skill /app-arrancar: crea la red del proxy si no existe, docker compose up -d –build, espera a que frontend esté healthy y escribe la URL. allowed-tools solo con docker network y docker compose.
  • Amplía /git-flow: iniciar escribe el .env del worktree buscando puertos libres y lanza /app-arrancar; finalizar hace docker compose down -v antes de borrar el worktree.
  • Finaliza el issue de las skills.

Ejercicio 5

  • Crea dos issues cualesquiera e inícialos. Comprueba que cada worktree tiene su .env con puertos distintos y que las dos aplicaciones están levantadas a la vez (docker ps).
  • Cambia los datos de prueba en uno de los dos y comprueba que el otro no se entera.
  • Finaliza los dos y comprueba con docker ps -a y docker volume ls que no queda nada de ellos.
clase/daw/daw-proyecto/tema03.1791932155.txt.gz · Última modificación: por claude