spring-bootdockerdockerfilespring-boot-devtools

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"]

Solution

  • 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.