docker / compose

Define and run multi-container applications with Docker
https://docs.docker.com/compose/
Apache License 2.0
33.59k stars 5.18k forks source link

[BUG] Docker compose does not interpret the .env inside the Spring Boot project. #12095

Open drakgoku opened 2 weeks ago

drakgoku commented 2 weeks ago

Description

When everything finally works fine locally, what I do is generate the jars and run docker compose.

After compiling the spring boot .jars and doing

docker compose -f docker-compose-dev.yml up --build -d

Spring Boot and Docker seem to for some reason not correctly render the spring boot .env and the Spring Boot variables do not arrive correctly.

This is my mysql image in my docker-compose, for mysql and a microservice called profile.

...
  profile_service:
    build:
      context: ../../          /profile
      dockerfile: Dockerfile
    container_name: c_profile_service
    ports:
      - "8202:8202"
    depends_on:
      - db_mysql
...
db_mysql:
    image: mysql:8.0.39
    container_name: c_db_mysql
    ports:
      - "3306:3306"
    environment:
      MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
      MYSQL_USER: ${MYSQL_USER}
      MYSQL_PASSWORD: ${MYSQL_PASSWORD}
    volumes:
      - mysql-data:/var/lib/mysql
      - ./init.sql:/docker-entrypoint-initdb.d/init.sql
...

The .env in my docker-compose-dev.yml is working fine, it's the Spring Boot one that's not working. Let's go to the Spring Boot profile project.

application.properties:

# Microservice
spring.application.name=${SPRING_APPLICATION_NAME}

# SERVER PORT
server.port=${SERVER_PORT}

# DATABASE
spring.datasource.url=${SPRING_DATASOURCE_URL}
spring.datasource.username=${SPRING_DATASOURCE_USERNAME}
spring.datasource.password=${SPRING_DATASOURCE_PASSWORD}
spring.datasource.driver-class-name=${SPRING_DATASOURCE_DRIVER_CLASS_NAME}
spring.jpa.properties.hibernate.dialect=${SPRING_JPA_PROPERTIES_HIBERNATE_DIALECT}

# SCHEME DATABASE SQL
# spring.sql.init.schema-locations=
# WRITE SQL TO DATABASE
spring.sql.init.mode=${SPRING_SQL_INIT_MODE}

spring.config.import=optional:classpath:.env[.properties]

Spring Boot .env int /resources/

"SPRING_APPLICATION_NAME=...
SERVER_PORT=...
SPRING_DATASOURCE_URL=jdbc:mysql://c_db_mysql:3306/....
SPRING_DATASOURCE_USERNAME=...
SPRING_DATASOURCE_PASSWORD=...
SPRING_DATASOURCE_DRIVER_CLASS_NAME=...
SPRING_JPA_PROPERTIES_HIBERNATE_DIALECT=org.hibernate.dialect.MySQLDialect
SPRING_SQL_INIT_MODE=always

This works correctly if I run the project without being launched as an image:

SPRING_DATASOURCE_URL=jdbc:mysql://localhost:3306/.... WORKS

But if I launch it from the image with

docker compose -f docker- compose-dev.yml up --build -d: 
SPRING_DATASOURCE_URL=jdbc:mysql://c_db_mysql: 3306/.... NOT WORKS

I get this error from console docker container c_db_mysql: java.lang.NullPointerException: Cannot invoke "org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(java.sql.SQLException, String)" because the return value of "org .hibernate.resource.transaction.backend.jdbc.internal.JdbcIsolationDelegate.sqlExceptionHelper()" is null

It seems that somehow docker is not correctly interpreting the .env that works locally. Why?

1 - If I run the image in docker and run the standalone project with: SPRING_DATASOURCE_URL=jdbc:mysql://localhost:3306/.... WORKS 2 - If I run the entire docker-compose-dev.yml, the .env variables don't seem to be read by docker.

Steps To Reproduce

1 - Run jars 2 - docker compose -f docker-compose-dev.yml up --build -d 3 - Error trying to access the database, variables arrive as null.

Compose Version

Docker Compose version v2.29.1-desktop.1

Docker Environment

Windows 11

Anything else?

No response

drakgoku commented 2 weeks ago

If I don't see a solution, I'll have to migrate to Kubernetes. What I think will be better than docker-compose.yml

ndeloof commented 2 weeks ago

Up to you to migrate to k8s and see if it is "better" (you'll discover a whole new universe of random issues :P)

About your issue, you should access your database using service name db_mysql , not c_db_mysql - I'm not sure where this prefix comes from ?

drakgoku commented 2 weeks ago

db_mysql = service name c_db_mysql = container name

My understanding is that it is the name of the container, I tried both. Neither works.

This isn't about discovering a new world. If I have to pay to do some tests... I'm not happy about it. Having to go looking for hosts with free Kubernetes... instead of things working normally... What do you want me to say?

drakgoku commented 2 weeks ago

It seems that the solution is this:

spring.jpa.properties.hibernate.boot.allow_jdbc_metadata_access=false

In the docker console it appears:

2024-08-31 20:14:19 2024-08-31T18:14:19.162Z WARN 1 --- [8202] [ main] JpaBaseConfiguration$JpaWebConfiguration : spring.jpa.open-in-view is enabled by default. Therefore, database queries may be performed during view rendering. Explicitly configure spring.jpa.open-in-view to disable this warning

Could someone explain to me why? Is there some incompatibility with Docker and jdbc metadata? ㅤ ㅤ ㅤ ㅤ In addition to the problem, I'm also thinking that the issue of putting "depends_on" in docker-compose.yml and "jdbc:mysql://[container]:3306/.." in spring boot is a bit annoying since it seems that spring only works based on the container. I think it creates a monolithic dependency.

I think it would be better without putting the depends_onand without the container for greater modularity... something like:

jdbc:mysql://domain:3306/.. 

I think it would be more appropriate without being tied to any container, rather you would be tied to the domain.

ㅤ ㅤ Note: I will leave this issue to your decision if you decide to close it or on the contrary if you want to contribute something extra and so if you want to keep it open and take it into account for a possible improvement or repair that can be done.

ndeloof commented 1 week ago

jdbc:mysql://[container]:3306/..

this is not container but service, which is actually a domain name container can be accessed (resolved by docker engine internal DNS)

drakgoku commented 1 week ago

My understanding is that when you define a service in Docker Compose, Docker creates a default network for your application and each container joins this network with the service name. Therefore, other containers can access this service using its service name / container as hostname.

https://docs.docker.com/compose/networking/

Docker networking Each container can now look up the service name web or db and get back the appropriate container's IP address. For example, web's application code could connect to the URL postgres://db:5432 and start using the Postgres database._ _It is important to note the distinction between HOST_PORT and CONTAINER_PORT. In the above example, for db, the HOST_PORT is 8001 and the container port is 5432 (postgres default). **Networked service-to-service communication uses the CONTAINER_PORT**. When HOST_PORT is defined, the service is accessible outside the swarm as well._ _Within the web container, your connection string to db would look like postgres://db:5432, and from the host machine, the connection string would look like postgres://{DOCKER_IP}:8001 for example postgres://localhost:8001 if your container is running locally.

In Docker Compose, I have defined the service as db_mysql, and within that service, you specify that the container will be called c_db_mysql. For inter-container communication, you would use the service name (db_mysql), but in my case, since I have specified a container name (c_db_mysql), I can use that name as well.

Both ways are correct. If you want an approximation, we can say that it is much better to use the service name.

ㅤ ㅤㅤ Even if you use the name of the service or container, it still seems monolithic to me, which was my question. I think I'll uncouple them and leave it like this: jdbc:mysql://domain:3306/.. I think it looks less tied to a docker image. For example, something simple if you do local tests: jdbc:mysql://localhost:3306/ or if you're on a domain jdbc:mysql://www.blizzard.com:3306/

This last way, without being tied to an image, seems less monolithic to me.

By the way, do you know anything about what I told you about jdbc metadata and Docker that variables arrive empty and because of that having to disable metadata?