gradle-docker-plugin
gradle-docker-plugin copied to clipboard
Add RUN command that is inserted before the COPY commands
Expected Behavior
Plugin should allow inserting RUN
instructions into the Dockerfile
before it inserts COPY libs, resources, classes
instructions to allow user executing commands like apt-get install
that are not invalidated after each Gradle project change.
Current Behavior
Currently, if one uses runCommand
with something costly like apt-get install
, these RUN
commands will be executed each time the Docker image is rebuilt due to code change.
Context
In our case this results in Docker image build being slow and potentially flaky (network-related) because RUN apt-get install
instructions are always executed if project source code is changed.
Steps to Reproduce (for bugs)
tasks.named("dockerCreateDockerfile") {
val task = (this as Dockerfile)
task.runCommand("""
apt-get update && \
apt-get install -y somepackage && \
apt-get clean
""".trimIndent())
}
Using configuration like this and then changing some source code in the project and running ./gradlew dockerBuildImage
results in RUN apt-get
instructions being always executed because they're added to the end of generated Dockerfile after the COPY
commands with code content.
Your Environment
Gradle Docker Plugin version 8.0.0
Currently I solved this by hacking around ListProperty
that stores instructions
:
tasks.named("dockerCreateDockerfile") {
val task = (this as Dockerfile)
// RUN needs to be inserted before COPY to reduce Docker build invalidations.
// See https://github.com/bmuschko/gradle-docker-plugin/issues/1093
val instructions = task.instructions.get().toMutableList()
instructions.add(
2,
RunCommandInstruction(
"""
apt-get update && \
apt-get install -y somepackage && \
apt-get clean
""".trimIndent()
)
)
task.instructions.value(instructions)
}
Not a fan of this solution, I think plugin should provide some sort of runBeforeCommand
that stabily inserts instruction before COPY libs, resources, classes
but preserves order of instructions inserted like this.
I'd argue that many users use runCommand
to install system packages with apt-get
/etc and they don't expect this instruction to be inserted after the COPY libs, resources, classes
and thus being always executed — wasting time on each build.
What do you think? :)
The solution you came up with has also been described in the user guide. I'd agree that achieving the goal could be easier. Right now, it requires knowledge about the generated Dockerfile.
There are a couple of things, we could do to make it easier:
- Expose methods that expose a way to get the path to the default working dir + the libs and classes dir. With that you should become easier to overwrite the default Dockerfile task completely with your own implementation.
- Make it easy to simply inject a Dockerfile that has been prebuilt.
- Expose a way to hooking into the generation of the Dockerfile more easily. I don't think the RUN instruction use case is the only one we should consider. The same could be said about LABELs etc.
RE 3: I'd have to think about it a bit to come up with a solution that is a) flexible enough, b) end user-friendly that makes it apparent when the code provided is executed. Let me know if you have some thoughts as well.
The last option is that you could simply write your own convention plugin with the functionality you need.
FWIW, here's my version of the same thing (kts syntax):
tasks.withType<Dockerfile> {
// Your custom runCommand instructions
runCommand("...")
// Move the COPY instructions to the end
// See https://github.com/bmuschko/gradle-docker-plugin/issues/1093
instructions.set(
instructions.get().sortedBy { instruction ->
if (instruction.keyword == CopyFileInstruction.KEYWORD) 1 else 0
}
)
}