We often use Docker Compose to run multiple applications with the database or with other services that the application is using, at the same time, using multiple Docker containers. From version 3.1 onwards, Spring Boot also supports Docker Compose, making it much easier to develop Spring Boot applications. How is it in detail? Let’s find out together in this tutorial!
First, I will create a new Spring Boot project with Spring Data JPA, PostgreSQL Driver and Docker Compose Support:
for example.
As you can see, when I create a Spring Boot project with Docker Compose Support, a compose.yaml file is also created.
Check this compose.yaml file, you will see a postgres service has been declared, as follows:
1 2 3 4 5 6 7 8 9 |
services: postgres: image: 'postgres:latest' environment: - 'POSTGRES_DB=mydatabase' - 'POSTGRES_PASSWORD=secret' - 'POSTGRES_USER=myuser' ports: - '5432' |
That’s because I declared to use the PostgreSQL Driver!
You need to edit the ports section a bit to expose port 5432 of the PostgreSQL database to the outside, as follows:
1 2 3 4 5 6 7 8 9 |
services: postgres: image: 'postgres:latest' environment: - 'POSTGRES_DB=mydatabase' - 'POSTGRES_PASSWORD=secret' - 'POSTGRES_USER=myuser' ports: - '5432:5432' |
Now, if you run this Spring Boot application, you will see Spring Boot automatically pull the Docker Image of PostgreSQL, the result is as follows:
Since my project currently has nothing, after pulling the Docker Image of PostgreSQL, my application will automatically shut down.
To make an example for this project, I will connect to the PostgreSQL database to get student information.
I will define the student table as follows:
1 2 3 4 5 6 7 |
CREATE TABLE student ( id INT PRIMARY KEY, name VARCHAR(100) ); INSERT INTO student(id, name) VALUES (1, 'Khanh'); |
To keep it simple, I will save the DDL of the student table to the db.sql file in the src/main/resources folder of the project and edit the postgres service in the compose.yaml file so that it automatically initializes the student table when running Docker Container for me, as follows:
1 2 3 4 5 6 7 8 9 10 11 |
services: postgres: image: 'postgres:latest' volumes: - ./src/main/resources/db.sql:/docker-entrypoint-initdb.d/init.sql environment: - 'POSTGRES_DB=mydatabase' - 'POSTGRES_PASSWORD=secret' - 'POSTGRES_USER=myuser' ports: - '5432:5432' |
Now, I will declare the database information for my application in the application.properties file as follows:
1 2 3 |
spring.datasource.url=jdbc:postgresql://localhost:5432/mydatabase spring.datasource.username=myuser spring.datasource.password=secret |
I will also declare the Student entity and the Repository class for this entity as follows:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
package com.huongdanjava.springboot; import jakarta.persistence.Entity; import jakarta.persistence.Id; @Entity public class Student { private @Id long id; private String name; public Student() { } public long getId() { return id; } public void setId(long id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } } |
and:
1 2 3 4 5 6 7 |
package com.huongdanjava.springboot; import org.springframework.data.jpa.repository.JpaRepository; public interface StudentRepository extends JpaRepository<Student, Long> { } |
Run the application with Spring Boot’s CommandLineRunner:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
package com.huongdanjava.springboot; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.CommandLineRunner; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; @SpringBootApplication public class SpringBootDockerComposeSupportApplication { @Autowired private StudentRepository studentRepository; public static void main(String[] args) { SpringApplication.run(SpringBootDockerComposeSupportApplication.class, args); } @Bean CommandLineRunner commandLineRunner(ApplicationContext ctx) { return args -> { studentRepository.findAll().forEach(s -> System.out.println(s.getName())); }; } } |
You will see the following results:
As you can see, my application has connected to the PostgreSQL Docker Container that Spring Boot launched when I ran the Spring Boot application.
Another support of Spring Boot for Docker Compose is that we do not need to declare PostgreSQL information in the application.properties file!
Information about the connection to the PostgreSQL database will be contained in the bean of the ConnectionDetails class.
You can delete the database configuration content that we declared above in the application.properties file and run the application again. You will also see the same result.
Try injecting the bean of the ConnectionDetails class and printing the information of the PostgreSQL database:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
package com.huongdanjava.springboot; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.CommandLineRunner; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.jdbc.JdbcConnectionDetails; import org.springframework.boot.autoconfigure.service.connection.ConnectionDetails; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; @SpringBootApplication public class SpringBootDockerComposeSupportApplication { @Autowired private StudentRepository studentRepository; @Autowired @Qualifier("jdbcConnectionDetailsForSpringBootDockerComposeSupportPostgres1") private ConnectionDetails connectionDetails; public static void main(String[] args) { SpringApplication.run(SpringBootDockerComposeSupportApplication.class, args); } @Bean CommandLineRunner commandLineRunner(ApplicationContext ctx) { return args -> { System.out.println(((JdbcConnectionDetails) connectionDetails).getJdbcUrl()); }; } } |
You will see the following results:
If you do not expose the PostgreSQL port to the outside but only declare it as follows in the compose.yaml file:
1 2 3 4 5 6 7 8 9 10 11 |
services: postgres: image: 'postgres:latest' volumes: - ./src/main/resources/db.sql:/docker-entrypoint-initdb.d/db.sql environment: - 'POSTGRES_DB=mydatabase' - 'POSTGRES_PASSWORD=secret' - 'POSTGRES_USER=myuser' ports: - '5432' |
PostgreSQL database will be exposed to the outside using random port and our application can also connect to this port. For my example, if you run it again, you will see the same result as me as follows:
This is very useful if you are developing microservices applications, each service uses a separate database. Random port will help us avoid port conflicts!