Ensure working of hot reload in local machine.
Maven:
To ensure that hot reload works locally, we need to
add spring-boot-devtools
dependency to pom.xml
which helps to re-run the application when the
changes are detected.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
</dependency>
Gradle:
For gradle add spring-boot-devtools
dependency to build.gradle
file
developmentOnly 'org.springframework.boot:spring-boot-devtools'
Maven / Gradle:
Note: If you're using IntelliJIDEA as development tool, after adding
devtools
dependency enable these properties:1.Navigate to - IntelliJIDEA -> Preferences -> Build, Execution, Deployment -> Compiler and enable
Build project automatically
.2.Navigate to - IntelliJIDEA -> Preferences -> Advanced Settings and enable
Allow auto-make to start even if developed application is currently running
.
After applying these changes run the application. While application is running, change
code base by adding some System.out.println
statement and save the changes. This time code changes
should be auto-compiled and updated without having to restart the application(hot reload).
Create Dockerfile
, docker-compose.yml
and .env
(used to pass values to variables used inside
docker-compose.yml) inside the working directory.
Then project structure is:
<working-dir>
├── ...
├── src
| └── ...
├── .env
├── Dockerfile
├── docker-compose.yml
└── README.md
Maven:
Gradle:
Now, copy the content from Dockerfile
, docker-compose.yml
and .env
files (based on build tool) to newly
created Dockerfile and docker-compose.yml files in your project.
Make appropriate changes in docker-compose.yml and .env like:
version: '3.8'
services:
spring-boot-postgres-maven:
image: <your-image-name>
container_name: <your-container-name>
....
...
DB_NAME=<your-db-name>
...
Maven/Gradle:
Now, run the application inside docker:
Simply do docker-compose up
inside working directory
$ docker-compose up
Then you may get error like permission denied exception for ./mvnw file
or permission denied exception for ./gradlew file
To solve this issue, execute the command:
chmod u+x ./mvnw
Similarly, for gradle do chmod u+x ./graldew
Again do a docker-compose up
, this time you don't get any errors but
for the first build it may take up to 7-10 minutes to pull
images(openjdk:11 and postgres:14.1-alpine) and download dependencies. If everything runs
successfully, by doing docker ps
you
would see a similar outcome(image and container names may differ) for maven / gradle:
➜ spring-postgres ✗ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
8247f3b42566 spring-boot-postgres-image "./mvnw spring-boot:…" 29 seconds ago Up 25 seconds 0.0.0.0:8000->8000/tcp, 0.0.0.0:8080->8080/tcp spring-boot-postgres-container
04a7dbf0c0e3 postgres:14.1-alpine "docker-entrypoint.s…" 4 minutes ago Up 4 minutes 5432/tcp postgres-container
Now, while the application is up and running inside docker, make the changes to the code base, then you would see that application running inside docker should restart automatically.
Logs inside docker after doing docker-compose up
:
.......
spring-boot-postgres-maven-container | Listening for transport dt_socket at address: 8000
spring-boot-postgres-maven-container | 06:32:14.319 [Thread-0] DEBUG org.springframework.boot.devtools.restart.classloader.RestartClassLoader - Created RestartClassLoader org.springframework.boot.devtools.restart.classloader.RestartClassLoader@4ab3d40a
spring-boot-postgres-maven-container |
spring-boot-postgres-maven-container | . ____ _ __ _ _
spring-boot-postgres-maven-container | /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
spring-boot-postgres-maven-container | ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
spring-boot-postgres-maven-container | \\/ ___)| |_)| | | | | || (_| | ) ) ) )
spring-boot-postgres-maven-container | ' |____| .__|_| |_|_| |_\__, | / / / /
spring-boot-postgres-maven-container | =========|_|==============|___/=/_/_/_/
spring-boot-postgres-maven-container | :: Spring Boot :: (v2.7.5)
.......
If you observe the log Listening for transport dt_socket at address: 8000
, that means application
is running in debug mode at port 8000 inside docker.
Application is running in debug mode because of command inside docker-compose:
docker-compose.yml
...
command: ./mvnw spring-boot:run -Dspring-boot.run.jvmArguments="-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:${DEBUG_PORT_ON_CONTAINER}"
...
In case of gradle to run application in debug mode add bootRun
task to build.gradle
:
bootRun task:
bootRun {
jvmArgs=["-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:8000"]
}
After that do docker-compose up
inside working directory, then the logs inside docker is:
...
spring-postgres-gradle-container | > Task :run
spring-postgres-gradle-container | Listening for transport dt_socket at address: 8000
spring-postgres-gradle-container | 06:32:14.319 [Thread-0] DEBUG org.springframework.boot.devtools.restart.classloader.RestartClassLoader - Created RestartClassLoader org.springframework.boot.devtools.restart.classloader.RestartClassLoader@4ab3d40a
spring-postgres-gradle-container |
spring-postgres-gradle-container | . ____ _ __ _ _
spring-postgres-gradle-container | /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
spring-postgres-gradle-container | ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
spring-postgres-gradle-container | \\/ ___)| |_)| | | | | || (_| | ) ) ) )
spring-postgres-gradle-container | ' |____| .__|_| |_|_| |_\__, | / / / /
spring-postgres-gradle-container | =========|_|==============|___/=/_/_/_/
spring-postgres-gradle-container | :: Spring Boot :: (v2.7.5)
.....
If you observe the log Listening for transport dt_socket at address: 8000
, that means application is running in debug mode at port 8000 inside docker.
Maven / Gradle:
Inside jvmArgs we have used the property address=*:8000
which tells from where we should attach debugger. Here *
is
placed in place of host that means we can attach to debugger from any host within the same network.
Then follow steps in Remote Debugging Using IntelliJIDEA to attach a debugger.
version: '3.8'
services:
spring-boot-postgres:
image: spring-boot-postgres-maven-image
container_name: spring-boot-postgres-maven-container
networks:
- spring-boot-postgres-network
build:
context: .
env_file: .env
depends_on:
- db
ports:
- ${APPLICATION_PORT_ON_DOCKER_HOST}:${APPLICATION_PORT_ON_CONTAINER}
- ${DEBUG_PORT_ON_DOCKER_HOST}:${DEBUG_PORT_ON_CONTAINER}
volumes:
- ./:/app
command: ./mvnw spring-boot:run -Dspring-boot.run.jvmArguments="-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:${DEBUG_PORT_ON_CONTAINER}"
db:
container_name: postgres-container
image: postgres:14.1-alpine
env_file: .env
ports:
- ${DB_PORT_ON_DOCKER_HOST}:${DB_PORT_ON_CONTAINER}
volumes:
- db:/var/lib/postgresql/data
networks:
- spring-boot-postgres-network
volumes:
db:
networks:
spring-boot-postgres-network:
Here each service acts as a new container. Since our application is dependent on db
service, we
need
to take care of a few things:
spring-boot-postgres
service shouldn't start beforedb
service. And that is why we useddepends_on
property underspring-boot-postgres
.spring-boot-postgres
anddb
services both have to be on the same network, so that they can communicate with each other. If we don't provide any network to services, they might run in isolated networks which leads to communication link failure between the application and the database.- Finally, for hot reloading of the app inside docker, our current directory(where the source code exists) should be mounted to the working directory inside the container.
volumes:
- ./:/app
Refer the spring-postgres sample project for complete source code.