Encounters error if imageId file exists, and the image it's referring is already built ("repoTags" is null)
Expected Behavior
DockerBuildImage should be able to successfully finish the build Docker image task.
Current Behavior
DockerBuildImage fails the build Docker image task if the image is already built previously and the following file exists: build/.docker/docker_buildImage-imageId.txt.
The error message is the following:
Cannot invoke "java.util.List.containsAll(java.util.Collection)" because "repoTags" is null
Context
I want to build a Docker image as a Gradle task so I can use it in a later task.
Steps to Reproduce (for bugs)
- Run the Docker build for the first time.
- Keep intact the generated files and images.
- Run the Docker build again to make it fail.
Your Environment
Gradle Docker plugin artifact: com.bmuschko.docker-remote-api:9.4.0 Gradle version: 8.11.1 OS: Windows 11
Possible workaround
Before every Docker image build, delete either the previously built Docker image, or the following file: build/.docker/docker_buildImage-imageId.txt.
Root Cause Analysis
I found the root cause of this issue, which is docker-remote-api packaging some of its dependencies but not renaming the packages of all of them. This leads to very difficult-to-debug behaviour:
docker-remote-apiis shading (i.e. including in the.jarand renaming the packages of)jackson-apiandjackson-databind, but is includingdocker-java-apianddocker-java-corein the.jarwithout renaming the packages. Because of this,docker-remote-apiputs the classes of those twodocker-javadependencies on the classpath (with their original package names!), but all jackson references—for example the@JsonPropertyannotations—in those class files link tocom.bmuschko.gradle.docker.shaded.com.fasterxml.jackson.….- We added another dependency to our build (testcontainers, specifically) that depends on
docker-java-api. This dependency brought the classes ofdocker-java-apito the classpath, replacing the classes packaged indocker-remote-api. However, the dependency did not replacedocker-java-core. - With this setup,
docker-remote-apiinvoked its version ofdocker-java-core, which, through itscom.bmuschko.gradle.docker.shaded.com.fasterxml.jackson.databind.ObjectMapper, searched forcom.bmuschko.gradle.docker.shaded.com.fasterxml.jackson.annotation.JsonPropertyannotations. However, the normal version ofdocker-java-apion our classpath had, of course,com.fasterxml.jackson.annotation.JsonPropertyannotations. This caused deserialization to fail and some properties to benull. Amazingly, this was only an issue whendocker-remote-apitried to check a pre-existing image. Hence, the workaround of removing thebuild/.dockerdirectory fixed all issues we were facing.
To illustrate the problem, here is the decompiled com.github.dockerjava.api.command.InspectImageResponse, as it is included in the .jar of docker-remote-api. Notice how InspectImageResponse has the normal package of docker-java-api, but references the renamed versions of jackson-api:
Improved Workaround
In our case, adding a dependency on docker-java-core to our buildSrc project fixes the issue. With that dependency on the classpath, all references to the shaded version of Jackson are replaced:
implementation(platform("com.github.docker-java:docker-java-bom:3.4.0"))
implementation("com.github.docker-java:docker-java-core")
However, this effectively disables the shading done in gradle-docker-plugin, which, I assume, exists for a reason.
Proposed fix
gradle-docker-plugin should either shade all its dependencies correctly (i.e. package and rename them), or not shade any of them. Including a dependency with its original package name but different compiled code will always lead to weird dependency errors when another dependency brings in docker-java. At the very least, other dependencies can effectively disable the shading, which defeats its purpose.