Thursday, December 28, 2023

Spring Boot, Docker compose on Mac OS Catalina - Gotcha . . .

Spring Boot 3.0 enhanced docker compose support with which container startup and destroy is taken care by spring boot and is quite convenient.

Anything I try first time, I hit road blocks. It's both good and bad. The good part is - it makes me explore things little deeper, which otherwise is not possible. The bad part is - it is frustrating and needs patience to get it to work.

Environment: Java 21, Spring Boot 3.2.0, maven 3.9.6 on macOS Catalina 10.15.7

Scenario

I wanted to add Postgres db service through docker compose. So, just added docker compose support to an existing simple Spring Boot 3.2.0 application. All that I had to do was simple. Add the dependency in pom.xml and a docker-compose.yml file in the root directory of the project. This is customizable through the property: spring.docker.compose.file.

pom.xml
... <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-docker-compose</artifactId> <scope>runtime</scope> <optional>true</optional> </dependency> ...

docker-compose.yaml
services: postgres: image: 'postgres16:16.0' environment: - 'POSTGRES_DB=boot-graalvm' - 'POSTGRES_USER=postgres' - 'POSTGRES_PASSWORD=s3cr3t' ports: - '5222:5432'

Application fails to run with the following error:
2023-12-27T08:58:48.534-05:00 ERROR 229 --- [ main] o.s.boot.SpringApplication : Application run failed org.springframework.boot.docker.compose.core.ProcessExitException: 'docker-compose version --format json' failed with exit code 1. Stdout: Stderr: Show version information and quit. Usage: version [--short] Options: --short Shows only Compose's version number. at org.springframework.boot.docker.compose.core.ProcessRunner.run(ProcessRunner.java:96) ~[spring-boot-docker-compose-3.2.0.jar:3.2.0] at org.springframework.boot.docker.compose.core.ProcessRunner.run(ProcessRunner.java:74) ~[spring-boot-docker-compose-3.2.0.jar:3.2.0] at org.springframework.boot.docker.compose.core.DockerCli$DockerCommands.getDockerComposeCommand(DockerCli.java:165) ~[spring-boot-docker-compose-3.2.0.jar:3.2.0] at org.springframework.boot.docker.compose.core.DockerCli$DockerCommands.(DockerCli.java:130) ~[spring-boot-docker-compose-3.2.0.jar:3.2.0] at org.springframework.boot.docker.compose.core.DockerCli.lambda$new$0(DockerCli.java:65) ~[spring-boot-docker-compose-3.2.0.jar:3.2.0] at java.base/java.util.HashMap.computeIfAbsent(HashMap.java:1228) ~[na:na] at org.springframework.boot.docker.compose.core.DockerCli.(DockerCli.java:64) ~[spring-boot-docker-compose-3.2.0.jar:3.2.0] at org.springframework.boot.docker.compose.core.DockerCompose.get(DockerCompose.java:92) ~[spring-boot-docker-compose-3.2.0.jar:3.2.0] at org.springframework.boot.docker.compose.lifecycle.DockerComposeLifecycleManager.getDockerCompose(DockerComposeLifecycleManager.java:149) ~[spring-boot-docker-compose-3.2.0.jar:3.2.0] at org.springframework.boot.docker.compose.lifecycle.DockerComposeLifecycleManager.start(DockerComposeLifecycleManager.java:110) ~[spring-boot-docker-compose-3.2.0.jar:3.2.0] at org.springframework.boot.docker.compose.lifecycle.DockerComposeListener.onApplicationEvent(DockerComposeListener.java:53) ~[spring-boot-docker-compose-3.2.0.jar:3.2.0] at org.springframework.boot.docker.compose.lifecycle.DockerComposeListener.onApplicationEvent(DockerComposeListener.java:35) ~[spring-boot-docker-compose-3.2.0.jar:3.2.0] at org.springframework.context.event.SimpleApplicationEventMulticaster.doInvokeListener(SimpleApplicationEventMulticaster.java:178) ~[spring-context-6.1.1.jar:6.1.1] at org.springframework.context.event.SimpleApplicationEventMulticaster.invokeListener(SimpleApplicationEventMulticaster.java:171) ~[spring-context-6.1.1.jar:6.1.1] at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:149) ~[spring-context-6.1.1.jar:6.1.1] at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:137) ~[spring-context-6.1.1.jar:6.1.1] at org.springframework.boot.context.event.EventPublishingRunListener.multicastInitialEvent(EventPublishingRunListener.java:136) ~[spring-boot-3.2.0.jar:3.2.0] at org.springframework.boot.context.event.EventPublishingRunListener.contextLoaded(EventPublishingRunListener.java:98) ~[spring-boot-3.2.0.jar:3.2.0] at org.springframework.boot.SpringApplicationRunListeners.lambda$contextLoaded$4(SpringApplicationRunListeners.java:72) ~[spring-boot-3.2.0.jar:3.2.0] at java.base/java.lang.Iterable.forEach(Iterable.java:75) ~[na:na] at org.springframework.boot.SpringApplicationRunListeners.doWithListeners(SpringApplicationRunListeners.java:118) ~[spring-boot-3.2.0.jar:3.2.0] at org.springframework.boot.SpringApplicationRunListeners.doWithListeners(SpringApplicationRunListeners.java:112) ~[spring-boot-3.2.0.jar:3.2.0] at org.springframework.boot.SpringApplicationRunListeners.contextLoaded(SpringApplicationRunListeners.java:72) ~[spring-boot-3.2.0.jar:3.2.0] at org.springframework.boot.SpringApplication.prepareContext(SpringApplication.java:431) ~[spring-boot-3.2.0.jar:3.2.0] at org.springframework.boot.SpringApplication.run(SpringApplication.java:322) ~[spring-boot-3.2.0.jar:3.2.0] at org.springframework.boot.SpringApplication.run(SpringApplication.java:1342) ~[spring-boot-3.2.0.jar:3.2.0] at org.springframework.boot.SpringApplication.run(SpringApplication.java:1331) ~[spring-boot-3.2.0.jar:3.2.0] [INFO] ------------------------------------------------------------------------ [INFO] BUILD FAILURE [INFO] ------------------------------------------------------------------------


The Fix

The issue was the older version of Docker Desktop 2.5.0.1 running on my laptop. The menu item Check for Updates... wouldn't even work. It seemed like I was stuck with very old version of Docker Desktop. The fix was to upgrade to a version that works. Yes, you have to find out the version that works for Mac OS Catalina. I tried and learned the fact that Docker Desktop latest version (4.26.1) doesn't run on Mac OS Catalina and Docker Desktop silently removed the support for Mac OS Catalina in 4.16.0. So the only version to upgrade for Mac OS Catalina is: 4.15.0.

Jeez, what a mess of higher elevated software development. It's just ridiculous!

There is no easy way even to find the version 4.15.0 download link. Finally found the links for both the versions: 2.5.0.1 that I had and 4.15.0 that I want to upgrade to. 

To downgrade to 2.5.0.1, here is the link: https://desktop.docker.com/mac/stable/49550/Docker.dmg
To upgrade to 4.15.0, here is the link: https://desktop.docker.com/mac/main/amd64/93002/Docker.dmg

TIPS

1. Spring Boot docker compose out-of-the-box looks for docker compose file named compose.yaml or docker-compose.yaml in the project root directory. If you want to place the file anywhere else under the project root folder, for instance like a docker sub-dir under project root, here is how the customization property should look like in the appropriate application.yml or application-<env>.yml related yml file:

spring: docker: compose: file: docker/docker-compose.yaml

2. Docker compose is enabled by default. When the dependent jar is found, it looks for compose.yaml/docker-compose.yaml. It can be disabled by setting explicitly the property spring.docker.enabled to false. Typically, docker compose is good for local development. If there are multiple profiles (environments) that the application is configured to run in, the application.properties or application.yml file should have docker compose disabled and it can be enabled in the appropriate profile that it's used, e.g local.

3. In the compose.yaml or docker-compose.yaml file, note that we specify the HOST_POST and CONTAINER_PORT for PostgreSQL in the format HOST_PORT:CONTAINER_PORT. The container port is 5432 (postgres default). The host port also can be 5432. But in case if you want to have multiple postgres databases managed by Docker for multiple applications/versions, you need to use different HOST_PORT for each image for both to be up and running at the same time. The HOST_PORT is the port you use to connect to the database.

4. Give a unique name for each postgres image if you need to have multiple versions of postgres instances managed by Docker. Otherwise, you will end up having just one. You can use version number in the image name to distinguish.

5. Check docker and docker-compose versions:
$ docker -v or docker --version
$ docker compose version

6. If the application fails with the following exception:

org.springframework.boot.docker.compose.lifecycle.ReadinessTimeoutException: Readiness timeout of PT2M reached while waiting for services

Add the following to docker-compose.yaml file 
services: postgres: image: 'postgres16:16.0' environment: - 'POSTGRES_DB=boot-graalvm' - 'POSTGRES_USER=postgres' - 'POSTGRES_PASSWORD=s3cr3t' ports: - '5222:5432' labels: org.springframework.boot.readiness-check.tcp.disable: true

References

No comments:

Post a Comment