Reputation: 1719
We've a Spring boot gradle project running inside a docker container, which uses a docker volume. Spring devtools live reload feature is used with following properties.
spring.devtools.restart.enabled=true
spring.devtools.restart.additional-paths=.
spring.devtools.restart.additional-exclude=src/main/java/**
We are using the docker volume to change the source files inside the container. The file src/main/resources/reload-trigger.txt
will be updated whenever a live reload is needed.
The container logs shows that the reloading works, but the changes are not affecting. On restarting the container, the changes reflected.
Eg steps:
Dockerfile
FROM gradle:5.6.2-jdk8
WORKDIR /app
COPY . /app
RUN ./gradlew getDeps
EXPOSE 8000
CMD ["gradle", "bootRun", "-PskipDownload=true"]
Upvotes: 4
Views: 8027
Reputation: 754
For maven projects because maven does not have an equivalent of Gradle's continuous build we can use plugin or shell scripts.
Here is what worked for me:
Dockerfile
FROM maven:3.8.7-openjdk-18-slim
RUN mkdir /api-app
WORKDIR /api-app
COPY pom.xml ./
RUN mvn dependency:go-offline
COPY docker-entrypoint.sh ./
COPY src ./src
COPY docker-entrypoint.sh ./
in the Dockerfile means copy the script file that watches the source code file change from the host to the image.
docker-entrypoint.sh
#!/bin/sh
export TERM=xterm
mvn spring-boot:run -Dspring-boot.run.jvmArguments="-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005" &
while true; do
watch -d -t -g "ls -lR . | sha1sum" && mvn compile
done
This is the script to watch the file changes and compile the java files into class files. So Spring Dev Tools notifies about the change and reloads/restarts the project.
docker-compose.yml
services:
api:
image: spring-api
container_name: spring-api
build: ./
volumes:
- ./src:/api-app/src
command: sh ./docker-entrypoint.sh
ports:
- 8080:8080
- 35729:35729
- 5005:5005
depends_on:
- db
environment:
SPRING_DATASOURCE_URL: jdbc:mysql://mysql-db:3306/my_db?createDatabaseIfNotExist=true
SPRING_DATASOURCE_USERNAME: root
SPRING_DATASOURCE_PASSWORD: password123
restart: always
db:
image: mysql:latest
container_name: mysql-db
ports:
- '3306:3306'
environment:
- MYSQL_ROOT_PASSWORD=password123
restart: always
add the following dependencies and plugin to the pom.xml file
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludeDevtools>false</excludeDevtools>
</configuration>
</plugin>
It is descripted more details here. but I used watch instead of inotifywait because inotifywait doesn't work for me.
Upvotes: 1
Reputation: 2076
If you are using vscode
and maven
with docker compose
you will need to make a bind-mount of the /target
directory. Remove the current /target
directory if there is already one in your host.
volumes:
# use /app if you use the same dir as WORKDIR in your Dockerfile
- ./src/main/:app/src/main
- ./src/test:/app/src/test
- ./target:/app/target
Also make sure you declare the correct application.properties
spring.devtools.livereload.enabled=true
spring.devtools.restart.enabled=true
spring.devtools.restart.poll-interval=2s
spring.devtools.restart.quiet-period=1s
Upvotes: -2
Reputation: 1719
The Spring Dev Tools hot reload mechanism only reloads/restarts the project from already built class files; it won't compile/build the source files. So inside a docker container, even if the source files are changed, the Java class files won't. Thus the change won't reflect and newly added GET API won't be published.
When the container is restarted, it again calls gradle bootRun
as specified in Dockerfile. This will build the changed Java files into class files and the change will be reflected.
When we use this in an IDE, the IDE (by default) builds the source file whenever it is changed. So Spring hot reload always loads the updated class files, but this won't happen outside an IDE, unless we have some mechanism to watch source changes and build them, like gradle build --continuous
These are my settings now.
application.properties
# scan for trigger file from root
# trigger file should not be in classpath, or it will go to infinite build loop
spring.devtools.restart.additional-paths=.
spring.devtools.restart.trigger-file=reload-trigger.txt
Dockerfile
FROM gradle:6.7-jdk11
WORKDIR /app
COPY . /app
EXPOSE 8000
# fetch dependencies
RUN chmod +x start.sh && gradle getDeps
# script which watches source file changes in background and executes bootRun
CMD ["sh", "start.sh"]
start.sh
This file should be in root as per the Dockerfile should not be in classpath
# buildAndReload task, running in background, watches for source changes
(sleep 60; gradle buildAndReload --continuous -PskipDownload=true -x Test)&
gradle bootRun -PskipDownload=true
build.gradle
# -----other codes above----- #
task buildAndReload {
dependsOn build
mustRunAfter build // buildAndReload must run after the source files are built into class files using build task
doLast {
new File(".", "reload-trigger.txt").text = "${System.currentTimeMillis()}" // update trigger file in root folder for hot reload
}
}
With these settings, whenever some source files are changed, the buildAndReload
task is being executed. As this task depends on build
task, the updated source are built into class file before that. This custom task, then updates the trigger file and Spring loads the updated class files and restarts the application.
Upvotes: 9