I'm quite new to Java and Spring. I'm looking for a containerized solution that watches the src
folder, rebuilds the project and takes advantage of Spring devtools hotswap to reload the changed classes.
I searched, but I just keep finding about production-ready containers, with separated steps for build and run. I tried to use 2 different containers, one with Gradle that keeps building (gradle build --continuous
) and one that executes the built result:
version: '3.7'
services:
builder:
image: gradle:jdk11
working_dir: /home/gradle/project
volumes:
- ./:/home/gradle/project
command: gradle build --continuous
api:
image: openjdk:11-slim
volumes:
- ./build/classes:/app
command: java -classpath /app/java/main com.example.Application
It fails because Java doesn't find the dependencies (Spring, devtools, h2, etc.) inside the api
container, and I don't know how to ask Gradle to include the external jars in the build folder. I want to do something like this, except that the example is outdated.
Still, I keep thinking that there might be a more elegant, simpler solution. It doesn't have to be with Gradle, it can be Maven if it works! :)
I know that many IDE have support for automatic builds and devtools, I just want to achieve it on Docker. This way, I would have a development workflow that is on repository, instead of on IDE's configuration, and virtually compatible with any dev environment. Is it a bad idea?
At last, I've found a solution that works quite well, with just one caveat. This is of course a development environment, meant to quickly change files, automatically build and refresh the Spring application. It is not for production.
The build process is delegated to a Gradle container that watches for changes. Since Gradle has Incremental Compilation, it should scale well even for big projects.
The application itself is executed on a openjdk:11-slim
. Since it runs the .class files, SpringBoot gets that it's dev-env and activates its devtools
.
Here's my docker-compose.yml
:
version: '3.7'
services:
builder:
image: gradle:jdk11
working_dir: /home/gradle/project
volumes:
- ./build:/home/gradle/project/build
- ./src:/home/gradle/project/src
- ./build.gradle:/home/gradle/project/build.gradle
command: gradle build --continuous -x test -x testClasses
api:
image: openjdk:13-alpine
volumes:
- ./build:/app
depends_on:
- builder
command: java -cp "/app/classes/java/main:/app/dependencies/*:/app/resources/main" com.example.Application
And here's my build.gradle
:
plugins {
id 'org.springframework.boot' version '2.2.1.RELEASE'
id 'io.spring.dependency-management' version '1.0.8.RELEASE'
id 'java'
}
group = 'com.example'
version = '0.1.0-SNAPSHOT'
sourceCompatibility = '11'
configurations {
developmentOnly
runtimeClasspath {
extendsFrom developmentOnly
}
compileOnly {
extendsFrom annotationProcessor
}
}
repositories {
mavenCentral()
}
task copyLibs(type: Copy) {
from configurations.runtimeClasspath
into "${buildDir}/dependencies"
}
build.dependsOn(copyLibs)
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'com.h2database:h2'
compileOnly 'org.projectlombok:lombok'
developmentOnly 'org.springframework.boot:spring-boot-devtools'
annotationProcessor 'org.projectlombok:lombok'
testImplementation('org.springframework.boot:spring-boot-starter-test') {
exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
}
}
test {
useJUnitPlatform()
}
All in all, it takes 5s for the whole thing to rebuild and hot-swap a change in the source code. Not webpack-like quick, but still acceptable. And the biggest advantage of having it this way is that it resides on code, and everyone can get it working, regardless of their workstation.
The caveat? On the first run, the build folder is empty and the api
container fails to start. You have to wait for builder
to complete its work, and then restart api
.
I'm still hoping for a better solution, and I encourage you to post everything that works smoother than this.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With