Search code examples
mysqlspring-bootdockerdocker-compose

Communications link failure when trying to connect a Spring Boot app to a MySQL when both of them run in Docker containers, using docker-compose


My aim is to connect two docker containers: one is a simple microservice, a Spring Boot App, another is a simple MySQL database running in docker container at local machine in Windows 10. After spending literally like 16 hours trying to connect my Spring Boot App and reading it everywhere possible and looking for solution in StackeOverflow, elsewhere in internet and in ChatGTP I still cannot find a solution how to make the connection to database in another container. I seeing people ask this question and when I tried answers that helped them, it is not working for me. All the time I’m getting the same problem: com.mysql.cj.jdbc.exceptions.CommunicationsException: Communications link failure

I have docker-compose.yaml:

version: '3.7'

    services:
      docker-mysql:
        image: mysql:latest
        ports:
          - "3307:3306"
        environment:
          MYSQL_DATABASE: wallet_microservice
          MYSQL_ROOT_PASSWORD: root
          SPRING_DATASOURCE_USERNAME: root
          SPRING_DATASOURCE_PASSWORD: root
    
        volumes:
          - ./src/main/resources/data/schema.sql:/docker-entrypoint-initdb.d/schema.sql
      myapp:
        build:
          dockerfile: Dockerfile
        ports:
          - "8080:8080"
    
        environment:
          SPRING_DATASOURCE_URL: jdbc:mysql://docker-mysql:3306/wallet_microservice?autoReconnect=true&useSSL=false
    
        depends_on:
          - docker-mysql

Dockerfile:

FROM eclipse-temurin:17
WORKDIR /app
COPY build/libs/*.jar app.jar
CMD ["java", "-jar", "app.jar"]

application.properties in the Spring boot app:

spring.datasource.url=jdbc:mysql://docker-mysql:3306/wallet_microservice
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.jpa.database-platform=org.hibernate.dialect.MySQLDialect

I could successfully connect to the database running in this container from the same Spring boot app if I run the app in localhost at the same machine as docker, then I only change datasource line in app’s application.properties to

spring.datasource.url=jdbc:mysql://localhost:3307/wallet_microservice

When I was consulting with ChatGTP for troubleshooting I got feedback: (all the things seems to be fine) Network Configuration:

  1. The docker-compose.yml file does not explicitly define a custom network, so Docker Compose will create a default network for the services. Since both myapp and docker-mysql are defined in the same docker-compose.yml file, they should be in the same network by default.
  2. Docker Service Dependency: Docker Compose's depends_on only ensures that services are started in the order specified. It does not guarantee that the service is fully initialized and ready to accept connections. Therefore, it's possible that myapp starts before docker-mysql is fully initialized. To address this, you may need to implement a retry mechanism in myapp to wait until the MySQL service is ready to accept connections. // here my comment: I tried to connect manually using Postman, still the same connection error, so timing seems not a reason.
  3. MySQL Configuration: The provided Docker Compose configuration sets up a MySQL container with the necessary environment variables (MYSQL_DATABASE, MYSQL_ROOT_PASSWORD, etc.). This setup seems correct.
  4. Firewall or Security Settings: Since both myapp and docker-mysql are running within Docker containers on the same host machine, firewall or security settings within the host machine are less likely to be the cause of the issue. However, it's still worth checking if there are any local firewall rules that might be interfering.
  5. Docker Compose Configuration: The docker-compose.yml file appears to be correctly configured with the necessary service definitions and port mappings. The port mapping for MySQL (3307:3306) ensures that port 3307 on the host is mapped to port 3306 in the Docker container, where MySQL is listening.

Solution

  • I suspect that this is because your app is trying to connect to the database before it is fully initialised.

    version: '3.7'
    
    services:
      docker-mysql:
        image: mysql:latest
        container_name: mysql
        environment:
          MYSQL_ROOT_PASSWORD: root
        healthcheck:
          test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
          interval: 5s
          timeout: 5s
          retries: 5
      myapp:
        image: mysql:latest
        container_name: myapp
        depends_on:
          docker-mysql:
            condition: service_healthy
        command: mysql -h docker-mysql -u root -p'root' -e 'SHOW DATABASES;'
    

    You need to use depends_on but it's not enough to check that the database service has started. You need to ensure that the actual database has initialised and is ready to accept connections.

    So create a healthcheck for the database service that will only pass once you are able to ping the database. Then make your app wait for the service to be marked as "healthy".

    In the docker-compose.yml I have replaced your app with a container that simply connects to the database and executes a simple SQL query. This will demonstrate that the database service is ready.

    You can fiddle with the values for interval, timeout and retries but the values supplied are a reasonable starting point.