Mike Wacker
09/24/2025, 10:57 PM# syntax=docker/dockerfile:1
FROM eclipse-temurin:21-jdk AS build
WORKDIR /src
COPY gradle/ gradle/
COPY gradle.properties gradlew ./
RUN ./gradlew --no-daemon --version
COPY buildSrc/ buildSrc/
RUN touch settings.gradle.kts
RUN ./gradlew --no-daemon build
COPY . .
RUN ./gradlew --no-daemon installDist
FROM eclipse-temurin:21-jre
WORKDIR /app
COPY --from=build /src/app/build/install/app/ ./
COPY --from=build /src/docker/config/ config/
CMD ["bin/app", "server", "config/config.yml"]
Aside: it still seems like the buildSrc tasks take a significant, non-zero amount of time in the RUN ./gradlew --no-daemon installDist layer, but overall, this layer does seem to be faster if you build buildSrc in an earlier layer.Thomas Broyer
09/25/2025, 7:00 AMgradle and not gradlew). The only downside is that you have to keep the versions in the Dockerfile and gradle/wrapper/gradle-wrapper.properties in sync.
I for one probably wouldn't split the buildSrc vs. actual build but if it makes a big difference then why not…
How about using a --mount=type=cache to share the dependencies (at a minimum) between builds?TheGoesen
09/25/2025, 7:48 AMbuildSrc/build and buildSrc/.gradle as a cached mounts like Thomas said. That way buildsrc compilation would not get skipped not because of dockers layer cache, but gradle would skipping compiling buildSrc as it would be UP-TO-DATE. Same but differerent..
You also should add your GRADLE_USER_HOME as a cache dir.
Other than that: if you really want to use dockers layer caching instead of gradle dependendency tracking, I dont think there is a correct answer, your dockerfile might work for some trivial gradle projects but might fail for more complex ones..
But its a bit intresting, gradle dockerhub page unfortunately does not have information on how to utilize cached mount effectivelyMike Wacker
09/25/2025, 6:08 PM# syntax=docker/dockerfile:1
FROM gradle:9.1.0-jdk21-noble AS build
WORKDIR /src
COPY . .
RUN --mount=type=cache,target=/home/gradle/.gradle \
gradle --no-daemon installDistTheGoesen
09/26/2025, 7:41 AM.gradle
3. if you do some include outside of your src build like includeBuild("../xjc-gradle-plugin")
you will have to replicate them in your docker build
4. the --no-deamon part is not needed. if you use --no-deamon gradle will terminate its deamon. If you omit --no-deamon the deamon gets terminated by docker finishing the build
5. Docker recommends using bind mounts instead of COPY . . in this case you would need your bind mount writeable, unless your gradle build doesnt write anything into its src dir, which would be fairly rare but probably possible...
6. If you are building many (docker) builds in parallel this could have some funny side effects, because than these builds may or may not share a single GRADLE_USER_HOME which could or could not cause gradle to wait until a build has finished in order to get a lockMike Wacker
09/26/2025, 10:16 PMCOPY . . (+ .dockerignore)--or else you can bring in a build folder that was built locally--but you also don't want to do a full build every time a small piece of code changes. I'm not sure it's even safe to run installDist locally and copy the install folder into a test-only image here; I'll just assume that's not safe.TheGoesen
09/27/2025, 2:12 PMMike Wacker
09/28/2025, 9:03 PMDockerfile.
a. Use COPY . . + Dockerfile.dockerignore to ensure that no local build artifacts get copied into the image.
b. Add some cache mounts to speed up rebuilds for local development.
2. Test the code--either locally or in a CI job--via a special Dockerfile.test that copies locally-built files from build/install (e.g., an E2E test that deploys the app and all its dependencies in a Docker network).
a. It ensures you only build the code once.
b. For a CI job, this is probably safe enough if the base OS is consistent, e.g., FROM gradle:9.1.0-jdk21-noble AS build for Dockerfile and runs-on: ubuntu-24.04 for the Github workflow.
For testing, we can also use avast's docker compose plugin, e.g.:
dockerCompose {
useComposeFiles = listOf("$projectDir/docker-compose.yml")
isRequiredBy(tasks.test)
}
tasks.named("composeBuild") {
dependsOn(tasks.installDist)
}