# Usa contenedores para el desarrollo con Java


## Prerrequisitos

Sigue los pasos para contenerizar tu aplicación en [Contenerizar tu aplicación](/guides/java/develop/containerize/).

## Descripción general

En esta sección, configurarás un entorno de desarrollo local
para la aplicación que contenerizaste en la sección anterior. Esto incluye:

- Agregar una base de datos local y persistir datos
- Crear un contenedor de desarrollo para conectar un depurador
- Configurar Compose para actualizar automáticamente tus servicios de Compose en ejecución a medida
  que editas y guardas tu código

## Agregar una base de datos local y persistir datos

Puedes usar contenedores para configurar servicios locales, como una base de datos. En esta sección, actualizarás el archivo `docker-compose.yaml` para definir un servicio de base de datos y un volumen para persistir los datos. Además, esta aplicación en particular utiliza una propiedad del sistema para definir el tipo de base de datos, por lo que tendrás que actualizar el `Dockerfile` para pasar la propiedad del sistema al iniciar la aplicación.

En el directorio del repositorio clonado, abre el archivo `docker-compose.yaml` en un IDE o editor de texto. Tu archivo de Compose tiene un servicio de base de datos de ejemplo, pero requerirá algunos cambios para tu aplicación específica.

En el archivo `docker-compose.yaml`, debes hacer lo siguiente:

- Descomentar todas las instrucciones de la base de datos. Ahora usarás un servicio de base de datos
  en lugar de almacenamiento local para los datos.
- Eliminar el elemento de nivel superior `secrets` así como el elemento dentro del servicio
  `db`. Este ejemplo utiliza la variable de entorno para la contraseña en lugar de secretos.
- Eliminar el elemento `user` del servicio `db`. Este ejemplo especifica el
  usuario en la variable de entorno.
- Actualizar las variables de entorno de la base de datos. Estas están definidas por la imagen de
  Postgres. Para más detalles, consulta la
  [Imagen oficial de Docker de Postgres](https://hub.docker.com/_/postgres).
- Actualizar la prueba de comprobación de estado (healthcheck) para el servicio `db` y especificar el usuario. Por
  defecto, la comprobación de estado utiliza el usuario root en lugar del usuario `petclinic`
  que definiste.
- Agregar la URL de la base de datos como una variable de entorno en el servicio `server`. Esto
  sobrescribe el valor predeterminado definido en
  `spring-petclinic/src/main/resources/application-postgres.properties`.

A continuación se muestra el archivo `docker-compose.yaml` actualizado. Se han eliminado todos los comentarios.

```yaml {hl_lines="7-29"}
services:
  server:
    build:
      context: .
    ports:
      - 8080:8080
    depends_on:
      db:
        condition: service_healthy
    environment:
      - POSTGRES_URL=jdbc:postgresql://db:5432/petclinic
  db:
    image: postgres:18
    restart: always
    volumes:
      - db-data:/var/lib/postgresql
    environment:
      - POSTGRES_DB=petclinic
      - POSTGRES_USER=petclinic
      - POSTGRES_PASSWORD=petclinic
    ports:
      - 5432:5432
    healthcheck:
      test: ["CMD", "pg_isready", "-U", "petclinic"]
      interval: 10s
      timeout: 5s
      retries: 5
volumes:
  db-data:
```

Abre el `Dockerfile` en un IDE o editor de texto. En la instrucción `ENTRYPOINT`,
actualiza la instrucción para pasar la propiedad del sistema como se especifica en el
archivo `spring-petclinic/src/resources/db/postgres/petclinic_db_setup_postgres.txt`.

```diff
- ENTRYPOINT [ "java", "org.springframework.boot.loader.launch.JarLauncher" ]
+ ENTRYPOINT [ "java", "-Dspring.profiles.active=postgres", "org.springframework.boot.loader.launch.JarLauncher" ]
```

Guarda y cierra todos los archivos.

Ahora, ejecuta el siguiente comando `docker compose up` para iniciar tu aplicación.

```console
$ docker compose up --build
```

Abre un navegador y visita la aplicación en [http://localhost:8080](http://localhost:8080). Deberías ver una aplicación sencilla para una clínica veterinaria (pet clinic). Explora la aplicación. Ve a **Veterinarians** y verifica que la aplicación esté conectada a la base de datos comprobando que puede listar a los veterinarios.

En la terminal, presiona `ctrl`+`c` para detener la aplicación.

## Dockerfile para desarrollo

El Dockerfile que tienes ahora es ideal para una imagen de producción pequeña y segura con
solo los componentes necesarios para ejecutar la aplicación. Al desarrollar, es posible que
quieras una imagen diferente que tenga un entorno diferente.

Por ejemplo, en la imagen de desarrollo puedes querer configurar la imagen para iniciar
la aplicación de modo que puedas conectar un depurador al proceso de Java en ejecución.

En lugar de gestionar múltiples Dockerfiles, puedes agregar una nueva etapa (stage). Tu
Dockerfile puede producir entonces tanto una imagen final lista para producción como
una imagen de desarrollo.

Reemplaza el contenido de tu Dockerfile con el siguiente.

```dockerfile {hl_lines="22-29"}
# syntax=docker/dockerfile:1

FROM eclipse-temurin:21-jdk-jammy as deps
WORKDIR /build
COPY --chmod=0755 mvnw mvnw
COPY .mvn/ .mvn/
RUN --mount=type=bind,source=pom.xml,target=pom.xml \
    --mount=type=cache,target=/root/.m2 ./mvnw dependency:go-offline -DskipTests

FROM deps as package
WORKDIR /build
COPY ./src src/
RUN --mount=type=bind,source=pom.xml,target=pom.xml \
    --mount=type=cache,target=/root/.m2 \
    ./mvnw package -DskipTests && \
    mv target/$(./mvnw help:evaluate -Dexpression=project.artifactId -q -DforceStdout)-$(./mvnw help:evaluate -Dexpression=project.version -q -DforceStdout).jar target/app.jar

FROM package as extract
WORKDIR /build
RUN java -Djarmode=layertools -jar target/app.jar extract --destination target/extracted

FROM extract as development
WORKDIR /build
RUN cp -r /build/target/extracted/dependencies/. ./
RUN cp -r /build/target/extracted/spring-boot-loader/. ./
RUN cp -r /build/target/extracted/snapshot-dependencies/. ./
RUN cp -r /build/target/extracted/application/. ./
ENV JAVA_TOOL_OPTIONS -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:8000
CMD [ "java", "-Dspring.profiles.active=postgres", "org.springframework.boot.loader.launch.JarLauncher" ]

FROM eclipse-temurin:21-jre-jammy AS final
ARG UID=10001
RUN adduser \
    --disabled-password \
    --gecos "" \
    --home "/nonexistent" \
    --shell "/sbin/nologin" \
    --no-create-home \
    --uid "${UID}" \
    appuser
USER appuser
COPY --from=extract build/target/extracted/dependencies/ ./
COPY --from=extract build/target/extracted/spring-boot-loader/ ./
COPY --from=extract build/target/extracted/snapshot-dependencies/ ./
COPY --from=extract build/target/extracted/application/ ./
EXPOSE 8080
ENTRYPOINT [ "java", "-Dspring.profiles.active=postgres", "org.springframework.boot.loader.launch.JarLauncher" ]
```

Guarda y cierra el `Dockerfile`.

En el `Dockerfile` has agregado una nueva etapa llamada `development` basada en la etapa `extract`. En esta etapa, copias los archivos extraídos a un directorio común y luego ejecutas un comando para iniciar la aplicación. En el comando, expones el puerto 8000 y declaras la configuración de depuración para la JVM para que puedas conectar un depurador.

## Usar Compose para desarrollar localmente

El archivo actual de Compose no inicia tu contenedor de desarrollo. Para hacer eso, debes actualizar tu archivo de Compose para apuntar a la etapa `development`. Además, actualiza el mapeo de puertos del servicio server para proporcionar acceso al depurador.

Abre el archivo `docker-compose.yaml` y agrega las siguientes instrucciones.

```yaml {hl_lines=["5","8"]}
services:
  server:
    build:
      context: .
      target: development
    ports:
      - 8080:8080
      - 8000:8000
    depends_on:
      db:
        condition: service_healthy
    environment:
      - POSTGRES_URL=jdbc:postgresql://db:5432/petclinic
  db:
    image: postgres:18
    restart: always
    volumes:
      - db-data:/var/lib/postgresql
    environment:
      - POSTGRES_DB=petclinic
      - POSTGRES_USER=petclinic
      - POSTGRES_PASSWORD=petclinic
    ports:
      - 5432:5432
    healthcheck:
      test: ["CMD", "pg_isready", "-U", "petclinic"]
      interval: 10s
      timeout: 5s
      retries: 5
volumes:
  db-data:
```

Ahora, inicia tu aplicación para confirmar que está funcionando.

```console
$ docker compose up --build
```

Por último, prueba tu punto de acceso (endpoint) de API. Ejecuta el siguiente comando curl:

```console
$ curl  --request GET \
  --url http://localhost:8080/vets \
  --header 'content-type: application/json'
```

Deberías recibir la siguiente respuesta:

```json
{
  "vetList": [
    {
      "id": 1,
      "firstName": "James",
      "lastName": "Carter",
      "specialties": [],
      "nrOfSpecialties": 0,
      "new": false
    },
    {
      "id": 2,
      "firstName": "Helen",
      "lastName": "Leary",
      "specialties": [{ "id": 1, "name": "radiology", "new": false }],
      "nrOfSpecialties": 1,
      "new": false
    },
    {
      "id": 3,
      "firstName": "Linda",
      "lastName": "Douglas",
      "specialties": [
        { "id": 3, "name": "dentistry", "new": false },
        { "id": 2, "name": "surgery", "new": false }
      ],
      "nrOfSpecialties": 2,
      "new": false
    },
    {
      "id": 4,
      "firstName": "Rafael",
      "lastName": "Ortega",
      "specialties": [{ "id": 2, "name": "surgery", "new": false }],
      "nrOfSpecialties": 1,
      "new": false
    },
    {
      "id": 5,
      "firstName": "Henry",
      "lastName": "Stevens",
      "specialties": [{ "id": 1, "name": "radiology", "new": false }],
      "nrOfSpecialties": 1,
      "new": false
    },
    {
      "id": 6,
      "firstName": "Sharon",
      "lastName": "Jenkins",
      "specialties": [],
      "nrOfSpecialties": 0,
      "new": false
    }
  ]
}
```

## Conectar un depurador

Usarás el depurador que viene con IntelliJ IDEA. Puedes usar la versión community de este IDE. Abre tu proyecto en IntelliJ IDEA, ve al menú **Run** y luego a **Edit Configuration**. Agrega una nueva configuración de tipo Remote JVM Debug similar a la siguiente:

![Java Connect a Debugger](/guides/java/develop/images/connect-debugger.webp)

Establece un punto de interrupción (breakpoint).

Abre `src/main/java/org/springframework/samples/petclinic/vet/VetController.java` y agrega un punto de interrupción dentro de la función `showResourcesVetList`.

Para iniciar tu sesión de depuración, selecciona el menú **Run** y luego **Debug _NameOfYourConfiguration_**.

![Debug menu](/guides/java/develop/images/debug-menu.webp?w=300)

Ahora deberías ver la conexión en los logs de tu aplicación de Compose.

![Compose log file ](/guides/java/develop/images/compose-logs.webp)

Ahora puedes llamar al endpoint del servidor.

```console
$ curl --request GET --url http://localhost:8080/vets
```

Deberías haber visto que el código se detiene en la línea marcada y ahora puedes usar el depurador igual que lo harías normalmente. También puedes inspeccionar y observar variables, establecer puntos de interrupción condicionales, ver trazas de la pila (stack traces) y hacer muchas otras cosas.

![Debugger code breakpoint](/guides/java/develop/images/debugger-breakpoint.webp)

Presiona `ctrl+c` en la terminal para detener tu aplicación.

## Actualizar servicios automáticamente

Usa Compose Watch para actualizar automáticamente tus servicios de Compose en ejecución a medida que
editas y guardas tu código. Para obtener más detalles sobre Compose Watch, consulta
[Usar Compose Watch](/compose/how-tos/file-watch/).

Abre tu archivo `docker-compose.yaml` en un IDE o editor de texto y luego agrega las
instrucciones de Compose Watch. A continuación se muestra el archivo `docker-compose.yaml`
actualizado.

```yaml {hl_lines="14-17"}
services:
  server:
    build:
      context: .
      target: development
    ports:
      - 8080:8080
      - 8000:8000
    depends_on:
      db:
        condition: service_healthy
    environment:
      - POSTGRES_URL=jdbc:postgresql://db:5432/petclinic
    develop:
      watch:
        - action: rebuild
          path: .
  db:
    image: postgres:18
    restart: always
    volumes:
      - db-data:/var/lib/postgresql
    environment:
      - POSTGRES_DB=petclinic
      - POSTGRES_USER=petclinic
      - POSTGRES_PASSWORD=petclinic
    ports:
      - 5432:5432
    healthcheck:
      test: ["CMD", "pg_isready", "-U", "petclinic"]
      interval: 10s
      timeout: 5s
      retries: 5
volumes:
  db-data:
```

Ejecuta el siguiente comando para ejecutar tu aplicación con Compose Watch.

```console
$ docker compose watch
```

Abre un navegador web y visita la aplicación en [http://localhost:8080](http://localhost:8080). Deberías ver la página de inicio de Spring Pet Clinic.

Cualquier cambio en los archivos fuente de la aplicación en tu máquina local se reflejará automáticamente en el contenedor en ejecución.

Abre `spring-petclinic/src/main/resources/templates/fragments/layout.html` en un IDE o editor de texto y actualiza el texto de navegación de `Home` agregando un signo de exclamación.

```diff
-   <li th:replace="~{::menuItem ('/','home','home page','home','Home')}">
+   <li th:replace="~{::menuItem ('/','home','home page','home','Home!')}">
```

Guarda los cambios en `layout.html` y luego puedes continuar desarrollando mientras el contenedor se reconstruye automáticamente.

Una vez que el contenedor se haya reconstruido y esté ejecutándose, refresca [http://localhost:8080](http://localhost:8080) y verifica que ahora aparece **Home!** en el menú.

Presiona `ctrl+c` en la terminal para detener Compose Watch.

## Resumen

En esta sección, analizaste el funcionamiento de una base de datos local y la persistencia de datos. También creaste una imagen de desarrollo que contiene el JDK y te permite conectar un depurador. Finalmente, configuraste tu archivo de Compose para exponer el puerto de depuración y configuraste Compose Watch para recargar en vivo tus cambios.

Información relacionada:

- [Referencia del archivo de Compose](/reference/compose-file/)
- [Compose Watch](/compose/how-tos/file-watch/)
- [Referencia de Dockerfile](/reference/dockerfile/)

## Próximos pasos

En la siguiente sección, verás cómo ejecutar pruebas unitarias en Docker.

