Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add RUN command that is inserted before the COPY commands #1093

Open
artem-zinnatullin opened this issue Sep 5, 2022 · 3 comments
Open

Add RUN command that is inserted before the COPY commands #1093

artem-zinnatullin opened this issue Sep 5, 2022 · 3 comments

Comments

@artem-zinnatullin
Copy link

artem-zinnatullin commented Sep 5, 2022

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

@artem-zinnatullin
Copy link
Author

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? :)

@bmuschko
Copy link
Owner

bmuschko commented Sep 6, 2022

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:

  1. 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.
  2. Make it easy to simply inject a Dockerfile that has been prebuilt.
  3. 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.

@BoD
Copy link

BoD commented Apr 9, 2023

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
        }
    )
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants