Building Minimal Images for Applications with Runtimes

Video demonstration of creating minimal images for applications with runtimes, such as Java

Tools used in this video


The Dockerfiles used in this video and other supporting documentation are available on GitHub.


Today, I’d like to talk about how to create minimal secure images when using a language that requires a runtime.

0:12 So here we’re thinking of things like Java, .NET or Python.

0:17 In these cases, you won’t be able to use the scratch image or the Chainguard or Google distroless static images, as they won’t include the files for the runtime.

0:28 In cases like these, we can still follow the approach of having a multistage build with the separate development and production image.

0:36 But this time our production image will need to have the files for the runtime installed.

0:42 So I’d like to work through an example with the Chainguard Maven and JRE images.

0:48 In this example, we’re using code from the pet clinic example application, but we’ve added our own Dockerfiles.

0:57 So you can see here we’re using a Chainguard Maven image to build.

1:02 All we really do is copy in the sources and then run the Maven wrapper script with a package argument and that’s going to produce our JAR file.

1:12 Then in the production image, all we really do is copy the JAR file that’s produced here into the image and set an appropriate entrypoint.

1:24 OK.

1:24 So let’s create this Docker image.

1:27 But the first thing I want to build is actually the “build” target.

1:30 So just building the build image to begin with. Now, because of the Docker cache, my last build that was pretty fast, but if you rebuild this yourself it’s gonna take a little bit longer.

1:43 OK.

1:44 Let’s take a look at that image.

1:47 I can see it’s pretty big.

1:48 So it’s 723 megabytes.

1:51 That’s not too surprising because you’ve got the JDK, Maven and all the resources required to build the JAR in there.

1:58 But it’s not something you’d want to transfer around too much.

2:02 OK.

2:03 So let’s take a look at the production image.

2:06 I’ll get rid of this target part and will rename it.

2:15 And again, it was cached, but this time it’s much smaller.

2:23 So that’s around half the size of the previous image.

2:28 Now, like the static images, we don’t have a shell or a package manager in this image.

2:35 Now, in this case, we didn’t specify a tag.

2:38 So we’ve used the latest version of both Maven and the JRE image.

2:45 This is fine for a lot of use cases, but sometimes you’ll want more control over the exact version of Java you’re using for the JDK and the runtime.

2:55 You can purchase a subscription to Chainguard images and that will give you access to tagged versions of the Java and Maven images.

3:04 But if that’s not feasible, another option is to use the Wolfi base image.

3:09 This allows us to effectively build our own Java image and specify the exact versions we require.

3:21 So we can modify the previous Dockerfile look like this.

3:27 where “FROM wolfi-base” is the major change.

3:31 And then we’re explicitly adding in open JDK version 17 and Maven version 3.9. We also set the Java HOME environment variable, but otherwise, it’s pretty much the same as before.

3:46 Then in the production image, we also use wolfi-base as the base and we add in open JDK version 17 again.

3:54 We also set the non root user.

3:57 So we don’t run as root in production and copy in the JAR as before.

4:04 OK.

4:04 So let’s build this image again.

4:11 I have a cached version.

4:12 So that was pretty fast.

4:15 Now note that this solution isn’t perfect.

4:18 And most notably, the final image is gonna include a shell and a package manager that wasn’t present in the previous build.

4:26 But interestingly, despite that this image is actually smaller than the other image.

4:37 Now, the reason for that is that the JRE image by default includes some locale data and that’s important to some Java applications and not important to some other ones.

4:51 So if you’re running a Java application where you need locale data, you’re going to need to add in a line, something like this and that will make your final image that bit bigger.

5:04 OK.

5:04 Just to round things off.

5:05 Let’s see the image running.

5:13 OK.

5:14 That seems to work.

5:16 So we’ve covered how you can use Chainguard images in applications that require run time.

5:21 And also what your options are if you need a specific version. I’ll add some links in the description that allow you to grab the code that I’ve gone through here.

5:31 But please let me know if you try this out and how it compares to other solutions.

5:36 Ok.

5:36 Thank you for listening.