I’m working on setting up an application with a React front-end and a Spring Boot back-end, alongside PgAdmin and PostgreSQL. I’ve successfully set up everything in Docker containers, including hot-reload for the front-end. However, setting up hot reload for the back-end has proven to be challenging.
I followed https://panigrahi-pratap.medium.com/spring-boot-live-reload-with-docker-db585fcca37f guide thoroughly, but still haven’t achieved my goal. Changes made to the Spring Boot code aren’t reflected in the running application within the Docker container.
Here is my setup:
Dockerfile in /backend2
# Use Maven with OpenJDK 21
FROM maven:3.9.9 AS build
# Set the working directory inside the container
WORKDIR /app
# Copy the pom.xml and the source code to the working directory
COPY pom.xml .
COPY src ./src
# Package the application (skip tests if necessary)
RUN mvn clean package -DskipTests
# Second stage: create a lightweight image for running the application
FROM eclipse-temurin:21-jdk-alpine
# Set the working directory inside the container
WORKDIR /app
# Copy the JAR file from the build stage to the runtime stage
COPY --from=build /app/target/*.jar /app/app.jar
# Expose the port the application runs on
EXPOSE 8080
# Run the Spring Boot application with DevTools enabled
ENTRYPOINT ["java",
"-Dspring.devtools.restart.enabled=true",
"-Dspring.devtools.livereload.enabled=true",
"-Dspring.devtools.remote.secret=mysecret",
"-jar", "/app/app.jar"]
application.properties in backend2
spring.application.name=backend2
spring.devtools.remote.secret=mysecret
Pom.xml in backend2:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.3.2</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.albion_tooling</groupId>
<artifactId>backend2</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>backend2</name>
<description>backend2</description>
<url/>
<licenses>
<license/>
</licenses>
<developers>
<developer/>
</developers>
<scm>
<connection/>
<developerConnection/>
<tag/>
<url/>
</scm>
<properties>
<java.version>21</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludeDevtools>false</excludeDevtools>
</configuration>
</plugin>
</plugins>
</build>
</project>
docker-compose.yml
version: '3.9'
services:
postgres:
container_name: database
image: postgres:16.4
hostname: localhost
ports:
- "${POSTGRES_PORT}:5432"
environment:
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: ${POSTGRES_DB}
volumes:
- albion_tooling_postgres-data:/var/lib/postgresql/data
- ./database/docker-entrypoint-initdb.d/:/docker-entrypoint-initdb.d/
restart: unless-stopped
pgadmin:
container_name: pgadmin
image: dpage/pgadmin4
depends_on:
- postgres
ports:
- "${PGADMIN_PORT}:80"
environment:
PGADMIN_DEFAULT_EMAIL: ${PGADMIN_DEFAULT_EMAIL}
PGADMIN_DEFAULT_PASSWORD: ${PGADMIN_DEFAULT_PASSWORD}
restart: unless-stopped
frontend:
container_name: frontend
build:
context: ./frontend
dockerfile: Dockerfile
environment:
CHOKIDAR_USEPOLLING: "true"
ports:
- "${FRONTEND_PORT}:3000"
volumes:
- ./frontend:/app
- /app/node_modules
restart: unless-stopped
backend:
container_name: backend
build:
context: ./backend2
dockerfile: Dockerfile
ports:
- "${BACKEND_PORT}:8080"
volumes:
- ./backend2/src:/app/src
- ./backend2/target:/app/target
- ~/.m2:/root/.m2
environment:
- JAVA_OPTS=-Dspring.devtools.restart.enabled=true -Dspring.devtools.livereload.enabled=true
- SPRING_PROFILES_ACTIVE=dev
depends_on:
- postgres
restart: unless-stopped
volumes:
albion_tooling_postgres-data:
2
Answers
I was able to resolve the issue on my own by making a few adjustments to my Docker setup. Specifically, I mapped the entire backend directory into the Docker container, which simplified the Dockerfile for the backend.
Backend Dockerfile:
docker-compose (only backend related)
The key was enabling hot-reload in IntelliJ. To do this, go to File > Settings > Build, Execution, Deployment > Compiler and check the option for "Build project automatically". With this setting enabled, the container will automatically restart whenever changes are made to the code.
I had the same requirement as you, as I was hoping to run my app in development as similar as possible to how it runs in production. Using the example provided in this article by JetBrains, I was able to (a) run/debug the app from Intellij, (b) trigger breakpoints (and switch to Intellij when this happened), (c) recompile automatically after changes when switching back to the browser, and (d) trigger LiveReload in the browser.
Aside from the "Build project automatically" setting, I also had the following additional configuration in
docker-compose.yml
:This allowed me to put all of my environment in the
.env
file, making it similar to how the app gets configured in production but allowing local customisations:The
spring.devtools.restart.enabled
setting was required in.env
because Spring Boot turns this off, as noted in the documentation:The additional
ports
configuration ensured that the LiveReload instance enabled by Spring Boot could be seen outside the container when accessing the running app athttp://localhost:8080
.