Ramanujan R
Ramanujan R

Reputation: 1719

Spring Boot live reload inside a docker container not working

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

Answers (3)

abdella
abdella

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

Helmer Barcos
Helmer Barcos

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

Ramanujan R
Ramanujan R

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

Related Questions