Container
Container (source: Schuger via Pixabay).

If you’re an enterprise developer who’s been eagerly anticipating the move to container technology within your organization, you have more than a passing interest in learning the basic concepts behind Docker and the commonly used orchestration frameworks around them. In this article, I’d like to expand on these basic concepts and provide some simple yet practical tips for building your first Docker images using the Java programming language.

Choose a small base JDK image size

Docker images are built by reading instructions from Dockerfile. You can find basic instructions for building your first Docker image here.

A common base image for including JDK in your application is the default openjdk:latest image. This is based on the Debian operating system. The size of this image is 640.9 MB. The JDK version can be seen by running the image:

docker run -it openjdk java -version

If you want to use Oracle JDK, then there is no official Docker image available at Docker Hub, for the right reason. So, you need to download Oracle JDK and package it in the image. A simplistic Dockerfile that shows how to package Oracle JDK is available at github.com/arun-gupta/docker-images/tree/master/oracle-jdk. An image built this way is 536.3 MB.

Alpine Linux is a stripped down Linux distribution built around musl libc and BusyBox. Docker Hub contains an Alpine-based OpenJDK and can be downloaded as openjdk:alpine. The size of this image is 144.9 MB. This is <25% of the default openjdk:latest image.

Why does smaller image size matter? It does not matter much in the disk space. But it becomes critical where containers are terminated, started on a different host, and the image then needs to be downloaded over the network. A bigger image size will mean higher latency for the containers to be started.

Similarly, openjdk:8-jre is 309 MB and openjdk:8-jre-alpine is 107.8 MB.

I’d like to offer a couple of takeaways here. Make sure to pick JDK or JRE, whatever is appropriate, as the base image. This will keep your image size small. And to further reduce the size, you may consider using an Alpine-based image instead of the default Debian-based one. If you do want a commercially supported JDK then you may have to make your own image. The base operating system for this image is discussed next.

Select the best base operating system image

Building a Docker image typically starts with a base operating system image. This would be mentioned in the FROM instruction of Dockerfile. In some cases, you may start with an already existing base image. In this case the base operating system image is already chosen for you. But if you need to choose a base operating system image, then the common images and their corresponding sizes are listed:

  • Ubuntu: 127.2 MB
  • CentOS: 196.8 MB
  • Debian: 123 MB
  • Alpine: 4.803 MB
  • BusyBox: 1.093 MB

Pick your base operating system carefully as it will add to your image size. Make sure to account for updating the operating system to the latest version of packages and dependencies as well.

In some cases, you may have to choose a commercially supported operating system such as Red Hat Enterprise Linux or Windows Server 2016. These would typically be offered at a vendor’s registry.

Clean up the build context

Docker CLI is given either a directory or URL as build context. This context is sent to the Docker daemon before the image is built. It is recommended to create Dockerfile in a new directory. Then, add only the files that will be included in the image in this directory. This provides a cleaner context to the docker build command and allows for a faster build of the image. Otherwise, scanning a directory with multiple files would unnecessarily slow the build.

If a new directory cannot be created or other files exist in the directory where the command is issued, then consider the .dockerignore file. Syntax of this file is similar to .gitignore and allows you to exclude files and directories from the build. For example, this may be required if Dockerfile is in the root of a Maven project. Then this file can be used to specify specific generated artifacts from the target directory.

Create your own image

You often need to create an image where the base image will start a component and your image will configure it. For example, the jboss/wildfly base image will start the WildFly application server and your image will create some JDBC resources in it. Another example is where couchbase Docker image starts the Couchbase server. In this case, the database needs to be manually configured with memory quota, administration credentials and other options. A new Docker image that provides a preconfigured Couchbase server would be very convenient. This is achieved in the couchbase/server:sandbox Docker image that uses couchbase as the base image. It configures the database using Couchbase REST API as explained in the Dockerfile.

Debug the image layout

You may also need to see how the filesystem within the container is laid out. This would mostly be for debugging purposes.

If the container is running then you can attach to it using the command

docker exec -it {cid} bash

And if the container is not running then you can use the command

docker run -it <image> bash

In both cases, a bash shell will open using the image’s filesystem. This allows you to inspect the container/image file system and make necessary updates, if need be.

Are you ready to Dockerize your first Java application? My book, Docker for Java Developers, explains how to package, deploy, and scale applications using Docker. In addition, the Docker for Java Developers tutorials on Github provide a self-paced, hands-on lab to build your first Java application.


This article is a collaboration between NGINX and O’Reilly. See our statement of editorial independence.

Article image: Container (source: Schuger via Pixabay).