Tabla de Contenidos
¡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.yamles 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 suVIRTUAL_HOST.compose.override.yamlcambia lo de desarrollo. Está en el repositorio y Compose lo carga solo. En el servidor,manage.sh(tema 6) usa-f compose.yamly 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 servecon la carpeta localfrontend/montada dentro: al guardar un fichero, el navegador se recarga.proxy.conf.jsonmanda/apiahttp://backend:8080. El volumen anónimo sobrenode_modulesevita 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).
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_NAMEes el prefijo de contenedores, redes y volúmenes de ese worktree. Dos proyectos distintos no se tocan.- Los puertos los busca
/git-flow iniciaral 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-clientyflyway-mysql. - Una migración
backend/src/main/resources/db/migration/V1__clientes.sqlcon una tabla. - Un
GET /api/clientesque 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.yamlno 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, ArchUnit) | backend/ del worktree | La base de datos del worktree, por localhost:$DB_PORT |
npx playwright test (E2E) | frontend/ del worktree | http://localhost:$FRONTEND_PORT |
Para que mvn test encuentre la base de datos del worktree, application.properties lee el puerto del entorno con un valor por defecto: spring.datasource.url=jdbc:mariadb:localhost:${DB_PORT:3306}/proyecto. Y se ejecuta con el .env cargado: set -a; . ../.env; set +a; mvn test.
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á.
===== Práctica =====
- Dockerfiles, compose.yaml, compose.override.yaml, proxy.conf.json y nginx.conf, generados por la IA y revisados contra lo de arriba. Añadir .env al .gitignore.
- La primera tabla con su migración, su endpoint, su página y su R__ de datos de prueba.
- AGENTS.md: cómo se levanta la aplicación, dónde corren Maven y Playwright y que el esquema va siempre en migraciones.
- Skill docker compose up -d –build/app-arrancar: levanta la aplicación del worktree actual (), espera a que el frontend esté sano y dice la URL. Si no existe la red del proxy, la crea.
- Ampliar iniciar/git-flow: escribe el .env del worktree con puertos libres y lanza /app-arrancar; finalizar hace docker compose down -v antes de borrar el worktree.
Prueba: iniciar dos issues, tener las dos aplicaciones levantadas a la vez en puertos distintos, cada una con sus datos de prueba, cambiar un componente de Angular en una y ver que se recarga sola, y finalizar las dos comprobando con docker ps y docker volume ls'' que no queda nada.
