Building a Docker image

Note
This repository contains the guide documentation source. To view the guide in published form, view it on the Open Liberty website.

Learn how to build a docker image. == What you’ll learn

You will learn how to write Dockerfiles, how to use the docker build to create Docker images and how to run instances of the images in Docker containers using docker run.

The application that you will be working with is an inventory service, which stores the information about various JVMs that run on different systems. Whenever a request is made to the inventory service to retrieve the JVM system properties of a particular host, the inventory service communicates with the system service on that host to get these system properties. The system properties are then stored and returned.

Checking out your microservices

The starting Java project, which you can find in the start directory, is a multi-module Maven project that’s made up of the system and inventory microservices. Each microservice resides in its own directory, start/system and start/inventory, respectively.

If you want to learn more about RESTful web services and how to build them, see Creating a RESTful web service for details about how to build the system service. The inventory service is built in a similar way.

To try out the application using Maven, first, navigate to the start directory and then run the following Maven goals to build the application and run it inside Open Liberty:

cd start
mvn install
mvn liberty:start-server

The starting point of the inventory service can be found at the http://localhost:9081/inventory/systems URL. It displays the current contents of the inventory.

The system service shows the system properties of your local JVM and can be found at the http://localhost:9080/system/properties URL.

The system can be added to the inventory by visiting the http://localhost:9081/inventory/systems/localhost URL.

After you are done checking out the application, stop the Open Liberty server:

mvn liberty:stop-server

Building your Docker images

A Docker image is a binary file, made up of multiple layers, used to execute code in a Docker container. Images are built from instructions, called Dockerfiles, to create a containerized version of the application.

A Dockerfile is a collection of instructions for building a Docker image that can then be run as a container. Every Dockerfile begins with a parent or base image on top of which various commands are run. For example, you can start your image from scratch and execute commands that download and install Java, or you can start from an image that already contains a Java installation.

Learn more about Docker on the official Docker page.

Install Docker on the official instructions page.

Creating your Dockerfiles

You will be creating two Docker images to run the inventory service and system service. The first step is to create Dockerfiles for both services.

Create the inventory/Dockerfile.
inventory/Dockerfile

inventory/Dockerfile

link:finish/inventory/Dockerfile[role=include]

The FROM instruction initializes a new build stage, which indicates the parent image of the image that has been built. If you don’t need a parent image, then you can use FROM scratch, which makes your image a base image.

In this case, you’re using the open-liberty image as your parent image, which comes with the latest Open Liberty runtime by default. You can find all different official images at the open-liberty DockerHub. To pull different open-liberty image, like kernel, you may define the FROM instructions as FROM open-liberty:kernel.

There are three different Open Liberty Docker image sets available on Docker Hub. You can learn more about them on the OpenLiberty Docker Images repository.

The ADD instruction copies local files and directories into the destination within your Docker image. It is structured as ADD <source> <destination>. In this case, the source is a tar.gz file that was created from running mvn install and the destination is a directory called /opt/ol where the server will be started. The ADD instruction unpacks local tar archives into the destination directory specified in the Dockerfile. This is one of the aspects that makes it different from the COPY instruction. The COPY instruction would keep a layer of the zipped archive as well as the unzipped contents of the archive, whereas ADD only keeps the unzipped contents of the archive. This server is started in the open-liberty Dockerfile.

The ADD instruction needs to use the user id 1001 and group 0 because the OpenLiberty image runs by default with the USER 1001 (non-root). Without this, the files and directories copied over would be owned by root.

Finally, the *.tar.gz files are copied over into /opt/ol.

The Dockerfile for the system service follows the same instructions as inventory service.

Create the system/Dockerfile.
system/Dockerfile

system/Dockerfile

link:finish/system/Dockerfile[role=include]

Creating your Docker image

Now that your microservices are complete and you have written your Dockerfiles, you will build your Docker images.

To build your image, you need Docker installed. For installation instructions, see the Official Docker Docs.

You will build your Docker image using the docker build command.

To build and containerize the application, start your Docker daemon and run the following commands:

docker build -t system system/.
docker build -t inventory inventory/.

The -t flag in the docker build command allows the docker images to be in name:tag format. The tag for an image describes the specific image version. To verify that the images are built, run the docker images command to list all local Docker images:

docker images

Your two images inventory and system should appear in the list of all Docker images:

REPOSITORY   TAG           IMAGE ID            CREATED             SIZE
inventory    latest        08fef024e986        4 minutes ago       471MB
system       latest        1dff6d0b4f31        5 minutes ago       470MB

Running your microservices in Docker containers

Now that you have your two images built, you will run your microservices in Docker containers:

docker run -d --name system -p 9080:9080 system
docker run -d --name inventory -p 9081:9081 inventory

The flags are described in the table below:

Flag Description

-d

Runs the container in the background.

--name

Specifies a name for the container.

-p

Maps the container ports to the host ports.

Next, run the docker ps command to verify that your containers are started:

docker ps

Make sure that your container is running and shows Up x seconds as its status:

CONTAINER ID   IMAGE            COMMAND                  CREATED          STATUS           PORTS                                        NAMES
2b584282e0f5   inventory:latest "/opt/ol/helpers/run…"   2 seconds ago    Up 1 second      9080/tcp, 9443/tcp, 0.0.0.0:9081->9081/tcp   inventory
99a98313705f   system:latest    "/opt/ol/helpers/run…"   3 seconds ago    Up 2 seconds     0.0.0.0:9080->9080/tcp, 9443/tcp             system

To access the application, point your browser to the http://localhost:9081/inventory/systems URL. An empty list is expected because no system properties are stored in the inventory yet.

Point your browser to the http://localhost:9080/system/properties URL. The system service shows the system properties of the system container JVM.

Next, you will retrieve the system containers IP address. When you start Docker, a default bridge network is created for running containers to be able to communicate. Run the following command to retrieve the system IP address:

docker network inspect bridge

You will find the address corresponding to IPv4Address:

"99a98313705fc9f8c21b8110900b8bee0427412373f8c3ef2d733c96d96ffb5f": {
    "Name": "system",
    "EndpointID": "e30b640eb214435c9bf08b23d7c3f851c88e909e4feffd1a893f14ae4d2f35a4",
    "MacAddress": "02:42:ac:11:00:02",
    "IPv4Address": "172.17.0.2/16",
    "IPv6Address": ""
}

In this case, the address for system is 172.17.0.2. Note down the [system-ip-address] in order to add the system properties to the inventory service.

Point your browser to the http://localhost:9081/inventory/systems/[system-ip-address] URL by replacing the [system-ip-address] with the value you obtained. You see a result in JSON format with the system properties of your local JVM. When you visit this URL, these system properties are automatically stored in the inventory. Go back to http://localhost:9081/inventory/systems and you see a new entry for [system-ip-address]. As you can see, the os.name is Linux because the base image was pulled from a Linux image.

If a problem occurs and your containers exit prematurely, the containers won’t appear in the container list that the docker ps command displays. Instead, your containers appear with an Exited status when running the docker ps -a command. Run the docker logs system and docker logs inventory commands to view the container logs for any potential problems and double-check that your Dockerfiles are correct. When you find the cause of the issue, remove the faulty containers with the docker rm system and docker rm inventory commands, rebuild your image, and start the containers again.

Testing the application

You can test your application manually or with automated tests that test the microservices running locally and against the docker container.

Create the SystemEndpointTest class.
system/src/test/java/it/io/openliberty/guides/system/SystemEndpointTest.java

SystemEndpointTest.java

link:finish/system/src/test/java/it/io/openliberty/guides/system/SystemEndpointTest.java[role=include]

The testGetProperties() method checks for a 200 response status from the system service endpoint.

Create the InventoryEndpointTest class.
inventory/src/test/java/it/io/openliberty/guides/inventory/InventoryEndpointTest.java

InventoryEndpointTest.java

link:finish/inventory/src/test/java/it/io/openliberty/guides/inventory/InventoryEndpointTest.java[role=include]
  • The testEmptyInventory() method checks that the inventory service has a total of 0 systems before anything is added to it.

  • The testHostRegistration() method checks that the system service was added to inventory properly.

  • The testSystemPropertiesMatch() checks that the system properties match what was added into the inventory.

  • The testUnknownHost() checks that an error is raised if an unknown host name is being added into the inventory service.

  • The systemServiceIp is the same value that you retrieved in the previous section to manually add the system service into the inventory service. This value will also be passed in when you run the tests.

Running the tests

Run the Maven verify goal to test the services running in the docker containers by replacing the [system-ip-address] with the value determined in the previous section.

mvn verify -Ddockerfile.skip=true -Dsystem.ip=[system-ip-address]

If the tests pass, you will see a similar output as the following:

-------------------------------------------------------
 T E S T S
-------------------------------------------------------
Running it.io.openliberty.guides.system.SystemEndpointTest
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.653 s - in it.io.openliberty.guides.system.SystemEndpointTest

Results:

Tests run: 1, Failures: 0, Errors: 0, Skipped: 0

-------------------------------------------------------
 T E S T S
-------------------------------------------------------
Running it.io.openliberty.guides.inventory.InventoryEndpointTest
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.935 s - in it.io.openliberty.guides.inventory.InventoryEndpointTest

Results:

Tests run: 1, Failures: 0, Errors: 0, Skipped: 0

When you are finished with the services, run the following commands to stop your containers.

docker stop inventory system
docker rm inventory system

Great work! You’re done!

You have just built Docker images for two microservices in Open Liberty.