-
Notifications
You must be signed in to change notification settings - Fork 446
Build distroless images using the DockerPlugin #1726
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
base: main
Are you sure you want to change the base?
Conversation
| override lazy val globalSettings: Seq[Setting[?]] = | ||
| Seq( | ||
| dockerBaseImage := "gcr.io/distroless/java", | ||
| dockerEntrypoint := Seq("/usr/bin/java"), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Initially I didn't want to override the entrypoint defined by the image, but by default is java -jar, expecting a single jar as CMD. This limits its use to simple JARs with no libraries, or to FatJAR/UberJARs.
With this entrypoint we can then specify arguments in the CMD.
| distrolessDebuggerPort := None | ||
| ) | ||
|
|
||
| override lazy val projectSettings: Seq[Setting[?]] = Seq(dockerCommands := { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Distroless images cannot run any script, like the on coming from BashStartScriptPlugin. This forces most of the configuration to be done at build time.
For example, the JDWP debugger must be specified when building so its corresponding cli argument can be included in the CMD command in the Dockerfile
| a.getOrElse(Int.MaxValue) < b.getOrElse(Int.MaxValue) | ||
| } | ||
|
|
||
| val cmd = if (dockerCmd.value.isEmpty) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In my original approach I duplicated dockerCmd to be an Option[Seq[String]]. This way users could define an empty CMD, or leave alone with a None. This in turn allowed this code to know explicitly what the user wanted.
With this preset, even if the user wants an empty CMD, the plugin will still calculate it and include in the Dockerfile. This, I feel, is a decent compromise. With a distroless image the CMD would probably always be defined to at least specify which JAR the user wants to run.
Otherwise, in the else branch, we expect the user to know exactly what they are doing.
| * @return | ||
| * FROM command | ||
| */ | ||
| private final def makeFromAs(dockerBaseImage: String, name: String): CmdLike = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Methods like this are duplicated from DockerPlugin. I didn't want to introduce a refactor where the methods are made public, as I wasn't sure if that would be the desired approach.
| private final def makeExposePorts( | ||
| exposedPorts: Seq[Int], | ||
| exposedUdpPorts: Seq[Int], | ||
| jdwpPort: Option[Int] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This method has a slight change from the one in DockerPlugin. If the JDWP port is specified, then it's added to the exposed list.
| * discovered main classes | ||
| * @return | ||
| */ | ||
| private def defineMainClass( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This method is similar to CommonStartScriptGenerator.generateStartScripts but here we require a single main class as it has to be defined at build time. The user cannot specify it at runtime like it was possible with the script.
| * Uses the `Universal / mappings` to generate the `Docker / mappings`. This overrides the `Docker / mappings` to | ||
| * skip scripts | ||
| */ | ||
| def mapGenericFilesToDocker: Seq[Setting[?]] = { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is an override on the method defined in DockerPlugin. Here we override the mappings to exclude binaries, or scripts, as they cannot be run on a distroless container.
| dockerLabels.value.map(makeLabel) ++ | ||
| dockerEnvVars.value.map(makeEnvVar) ++ | ||
| makeExposePorts(dockerExposedPorts.value, dockerExposedUdpPorts.value, distrolessDebuggerPort.value) ++ | ||
| Seq(makeEntrypoint(dockerEntrypoint.value), makeCmd(cmd)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The commands defined by the plugin are a subset of the ones available on DockerPlugin.
These are the most important changes:
- There is not multistage build. Since this was based on
DockerPermissionStrategy, which in some cases it requires a RUN while building, it doesn't make sense in this context as there is no way to run anything in a distroless image to change permissions. - There are no exposed volumes. Currently that's implemented with several RUN commands. I guess that if it would be desired, then defining just
VOLUMEwithout the RUNs would be possible. - There are no
makeUseror USER. This is due to how users are provided/created by the base distroless image. There are different tags for root and nonroot users.
|
Hi @nMoncho Thanks a lot for your contribution 🥳 ❤️ I'll try to take a look ASAP. |
|
This PR started to failed due to what's mentioned in #1679. Specifically #1679 (comment) |
Hello,
I'd like to submit this PR to allow users create Distroless images. This was mentioned/requested in #1105.
After playing around with the keys provided by the
DockerPlugin, I think this is the easiest approach to maintain. My original approach duplicated a lot of code from the existing plugin. With the approach outlined in this PR the work is mostly done by defining presets for existing Settings and Tasks.I didn't add any tests nor documentation, as I hope this can be the starting point of a conversation.