git clone https://github.com/openliberty/guide-containerize.git
cd guide-containerize
Containerizing microservices
Prerequisites:
Learn how to containerize and run your microservices with Open Liberty using Docker.
What you’ll learn
You can easily deploy your microservices in different environments in a lightweight and portable manner by using containers. From development to production and across your DevOps environments, you can deploy your microservices consistently and efficiently with containers. You can run a container from a container image. Each container image is a package of what you need to run your microservice or application, from the code to its dependencies and configuration.
You’ll learn how to build container images and run containers using Docker for your microservices. You’ll construct Dockerfile
files, create Docker images by using the docker build
command, and run the image as Docker containers by using docker run
command.
The two microservices that you’ll be working with are called system
and inventory
. The system
microservice returns the JVM system properties of the running container. The inventory
microservice adds the properties from the system
microservice to the inventory. This guide demonstrates how both microservices can run and communicate with each other in different Docker containers.
Getting started
The fastest way to work through this guide is to clone the Git repository and use the projects that are provided inside:
The start
directory contains the starting project that you will build upon.
The finish
directory contains the finished project that you will build.
Packaging your microservices
Navigate to the start
directory to begin. You can find the starting Java project in the start
directory. It is a multi-module Maven project that is made up of the system
and inventory
microservices. Each microservice lives in its own corresponding directory, system
and inventory
.
To try out the application by using Maven, run the following Maven goals to build the application and run it inside Open Liberty:
mvn install
mvn liberty:start-server
Notice that the <packageFile>
tag in the pom.xml
file configures the microservices to be packaged in an archive form. The system
and inventory
microservices including the application WAR
and server configuration files are archived into system/target/system.tar.gz
and inventory/target/inventory.tar.gz
.
To access the inventory
service, which displays the current contents of the inventory, see http://localhost:9081/inventory/systems.
The system
service shows the system properties of the running JVM and can be found at http://localhost:9080/system/properties.
The system properties of your localhost can be added to the inventory
at http://localhost:9081/inventory/systems/localhost.
After you are done checking out the application, stop the Open Liberty server:
mvn liberty:stop-server
pom.xml
1<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
2 <modelVersion>4.0.0</modelVersion>
3
4 <parent>
5 <groupId>net.wasdev.wlp.maven.parent</groupId>
6 <artifactId>liberty-maven-app-parent</artifactId>
7 <version>RELEASE</version>
8 </parent>
9
10 <groupId>io.openliberty.guides</groupId>
11 <artifactId>guide-containerize</artifactId>
12 <version>1.0-SNAPSHOT</version>
13 <packaging>pom</packaging>
14
15 <properties>
16 <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
17 <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
18 <maven.compiler.source>1.8</maven.compiler.source>
19 <maven.compiler.target>1.8</maven.compiler.target>
20
21 <!-- Plugins -->
22 <version.maven-war-plugin>3.2.2</version.maven-war-plugin>
23 <version.download-maven-plugin>1.4.0</version.download-maven-plugin>
24 <version.maven-surefire-plugin>3.0.0-M1</version.maven-surefire-plugin>
25 <version.maven-failsafe-plugin>3.0.0-M1</version.maven-failsafe-plugin>
26 <!-- OpenLiberty runtime -->
27 <version.openliberty-runtime>RELEASE</version.openliberty-runtime>
28 <sys.service.http.port>9080</sys.service.http.port>
29 <sys.service.https.port>9443</sys.service.https.port>
30 <inv.service.http.port>9081</inv.service.http.port>
31 <inv.service.https.port>9444</inv.service.https.port>
32 </properties>
33
34 <!-- Shared dependencies. -->
35 <dependencyManagement>
36 <dependencies>
37 <dependency>
38 <groupId>io.openliberty.features</groupId>
39 <artifactId>features-bom</artifactId>
40 <version>RELEASE</version>
41 <type>pom</type>
42 <scope>import</scope>
43 </dependency>
44 <!-- For tests -->
45 <dependency>
46 <groupId>junit</groupId>
47 <artifactId>junit</artifactId>
48 <version>4.12</version>
49 <scope>test</scope>
50 </dependency>
51 <dependency>
52 <groupId>org.apache.cxf</groupId>
53 <artifactId>cxf-rt-rs-client</artifactId>
54 <version>3.2.6</version>
55 <scope>test</scope>
56 </dependency>
57 <dependency>
58 <groupId>org.apache.cxf</groupId>
59 <artifactId>cxf-rt-rs-extension-providers</artifactId>
60 <version>3.2.6</version>
61 <scope>test</scope>
62 </dependency>
63 <dependency>
64 <groupId>org.glassfish</groupId>
65 <artifactId>javax.json</artifactId>
66 <version>1.0.4</version>
67 <scope>test</scope>
68 </dependency>
69 <dependency>
70 <groupId>org.apache.commons</groupId>
71 <artifactId>commons-lang3</artifactId>
72 <version>3.0</version>
73 <scope>compile</scope>
74 </dependency>
75 <!-- Support for JDK 9 and above -->
76 <dependency>
77 <groupId>javax.xml.bind</groupId>
78 <artifactId>jaxb-api</artifactId>
79 <version>2.3.1</version>
80 <scope>provided</scope>
81 </dependency>
82 <dependency>
83 <groupId>com.sun.xml.bind</groupId>
84 <artifactId>jaxb-core</artifactId>
85 <version>2.3.0.1</version>
86 <scope>provided</scope>
87 </dependency>
88 <dependency>
89 <groupId>com.sun.xml.bind</groupId>
90 <artifactId>jaxb-impl</artifactId>
91 <version>2.3.2</version>
92 <scope>provided</scope>
93 </dependency>
94 <dependency>
95 <groupId>javax.activation</groupId>
96 <artifactId>activation</artifactId>
97 <version>1.1.1</version>
98 <scope>provided</scope>
99 </dependency>
100 </dependencies>
101 </dependencyManagement>
102
103 <profiles>
104 <!-- mvn.cmd for windows systems -->
105 <profile>
106 <id>windowsExtension</id>
107 <activation>
108 <os><family>Windows</family></os>
109 </activation>
110 <properties>
111 <mvn.extension>.cmd</mvn.extension>
112 </properties>
113 </profile>
114 <!-- just mvn for other systems -->
115 <profile>
116 <id>nonWindowsExtension</id>
117 <activation>
118 <os><family>!Windows</family></os>
119 </activation>
120 <properties>
121 <mvn.extension></mvn.extension>
122 </properties>
123 </profile>
124 </profiles>
125
126 <build>
127 <pluginManagement>
128 <plugins>
129 <plugin>
130 <groupId>org.apache.maven.plugins</groupId>
131 <artifactId>maven-war-plugin</artifactId>
132 <version>${version.maven-war-plugin}</version>
133 <configuration>
134 <failOnMissingWebXml>false</failOnMissingWebXml>
135 <packagingExcludes>pom.xml</packagingExcludes>
136 </configuration>
137 </plugin>
138 <!-- Liberty Maven plugin -->
139 <plugin>
140 <groupId>net.wasdev.wlp.maven.plugins</groupId>
141 <artifactId>liberty-maven-plugin</artifactId>
142 <configuration>
143 <assemblyArtifact>
144 <groupId>io.openliberty</groupId>
145 <artifactId>openliberty-runtime</artifactId>
146 <version>RELEASE</version>
147 <type>zip</type>
148 </assemblyArtifact>
149 <skip>true</skip>
150 <!-- tag::packageFile[] -->
151 <packageFile>
152 ${project.build.directory}/${app.name}.tar.gz
153 </packageFile>
154 <!-- end::packageFile[] -->
155 </configuration>
156 </plugin>
157 <!-- Plugin to run unit tests -->
158 <plugin>
159 <groupId>org.apache.maven.plugins</groupId>
160 <artifactId>maven-surefire-plugin</artifactId>
161 <version>${version.maven-surefire-plugin}</version>
162 <executions>
163 <execution>
164 <phase>test</phase>
165 <id>default-test</id>
166 <configuration>
167 <excludes>
168 <exclude>**/it/**</exclude>
169 </excludes>
170 <reportsDirectory>
171 ${project.build.directory}/test-reports/unit
172 </reportsDirectory>
173 </configuration>
174 </execution>
175 </executions>
176 </plugin>
177 <!-- Plugin to run functional tests -->
178 <plugin>
179 <groupId>org.apache.maven.plugins</groupId>
180 <artifactId>maven-failsafe-plugin</artifactId>
181 <version>${version.maven-failsafe-plugin}</version>
182 <executions>
183 <execution>
184 <phase>integration-test</phase>
185 <id>integration-test</id>
186 <goals>
187 <goal>integration-test</goal>
188 </goals>
189 <configuration>
190 <includes>
191 <include>**/it/**</include>
192 </includes>
193 <systemPropertyVariables>
194 <system.ip>${system.ip}</system.ip>
195 <sys.http.port>
196 ${sys.service.http.port}
197 </sys.http.port>
198 <inv.http.port>
199 ${inv.service.http.port}
200 </inv.http.port>
201 </systemPropertyVariables>
202 </configuration>
203 </execution>
204 <execution>
205 <id>verify-results</id>
206 <goals>
207 <goal>verify</goal>
208 </goals>
209 </execution>
210 </executions>
211 <configuration>
212 <summaryFile>
213 ${project.build.directory}/test-reports/it/failsafe-summary.xml
214 </summaryFile>
215 <reportsDirectory>
216 ${project.build.directory}/test-reports/it
217 </reportsDirectory>
218 </configuration>
219 </plugin>
220 </plugins>
221 </pluginManagement>
222 </build>
223
224 <modules>
225 <module>system</module>
226 <module>inventory</module>
227 </modules>
228
229</project>
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.
Building your Docker images
A Docker image is a binary file. It is made up of multiple layers and is used to run code in a Docker container. Images are built from instructions in 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. As each instruction is run in a Dockerfile
, a new Docker layer is created. These layers, which are known as intermediate images, are created when a change is made to your Docker image.
Every Dockerfile
begins with a parent or base image over which various commands are run. For example, you can start your image from scratch and run commands that download and install a Java runtime, or you can start from an image that already contains a Java installation.
Learn more about Docker on the official Docker page.
Install Docker by following the instructions on the official 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 theDockerfile
for the inventory service.inventory/Dockerfile
inventory/Dockerfile
1# tag::from[]
2FROM open-liberty
3# end::from[]
4# tag::add[]
5# tag::userID[]
6ADD --chown=1001:0 \
7# end::userID[]
8 # tag::source[]
9 target/inventory.tar.gz \
10 # end::source[]
11 # tag::destination[]
12 /opt/ol
13 # end::destination[]
14# end::add[]
The FROM
instruction initializes a new build stage, which indicates the parent image of the built image. 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 Open Liberty runtime by default. You can find all different official images at open-liberty Docker Hub. To pull a different open-liberty image, like kernel
, define the FROM
instruction as FROM open-liberty: kernel
.
During the build, use a single ADD
command instead of using multiple COPY
commands to transfer configuration files and the application because the application is packaged into an archive. It eliminates multiple layers as the Docker image is created.
The ADD
instruction is structured as ADD
[--chown=<user>:<group>]
<source>
<destination>
. It copies local files and directories into the specified destination within your Docker image. If the source is an archive, the ADD
instruction unpacks it into the destination directory. In this case, the inventory
application archive inventory.tar.gz
, created from running mvn install
previously, is extracted to the destination directory /opt/ol
.
The ADD
instruction needs to use the user ID 1001
and group 0
because the open-liberty
image runs by default with the USER 1001
(non-root) user for security purposes. Otherwise, the files and directories that are copied over are owned by the root user.
The Dockerfile
for the system
service follows the same instructions as the inventory
service, except that the system.tar.gz
archive is copied over into /opt/ol
.
Create theDockerfile
for the system service.system/Dockerfile
system/Dockerfile
1FROM open-liberty
2# tag::add[]
3ADD --chown=1001:0 target/system.tar.gz /opt/ol
4# end::add[]
Building your Docker image
Now that your microservices are packaged and you have written your Dockerfiles, you will build your Docker images by using the docker build
command. To build your image, you need to have Docker installed and your Docker daemon started.
Run the following commands to build container images for your application:
docker build -t system:1.0-SNAPSHOT system/.
docker build -t inventory:1.0-SNAPSHOT inventory/.
The -t
flag in the docker build
command allows the Docker image to be labeled (tagged) in the name[:tag]
format. The tag for an image describes the specific image version. If the optional [:tag]
tag is not specified, the latest
tag is created by default.
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 1.0-SNAPSHOT 08fef024e986 4 minutes ago 471MB system 1.0-SNAPSHOT 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:1.0-SNAPSHOT
docker run -d --name inventory -p 9081:9081 inventory:1.0-SNAPSHOT
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 host ports to the container ports. For example: |
Next, run the docker ps
command to verify that your containers are started:
docker ps
Make sure that your containers are running and show Up
as their status:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 2b584282e0f5 inventory:1.0-SNAPSHOT "/opt/ol/helpers/run…" 2 seconds ago Up 1 second 9080/tcp, 9443/tcp, 0.0.0.0:9081->9081/tcp inventory 99a98313705f system:1.0-SNAPSHOT "/opt/ol/helpers/run…" 3 seconds ago Up 2 seconds 0.0.0.0:9080->9080/tcp, 9443/tcp system
If a problem occurs and your containers exit prematurely, the containers don’t appear in the container list that the docker ps
command displays. Instead, your containers appear with an Exited
status when they run the docker ps -a
command. Run the docker logs system
and docker logs inventory
commands to view the container logs for any potential problems. Run the docker stats system
and docker stats inventory
commands to display a live stream of usage statistics for your containers. You can also double-check that your Dockerfiles are correct. When you find the cause of the issues, remove the faulty containers with the docker rm system
and docker rm inventory
commands. Rebuild your images, and start the containers again.
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.
Next, retrieve the system
container’s IP address by using the system
container’s name that is defined when it ran the Docker containers. Run the following command to retrieve the system
IP address:
docker inspect -f "{{.NetworkSettings.IPAddress }}" system
You find the system
container’s IP address:
172.17.0.2
In this case, the IP address for the system
service is 172.17.0.2
. Take note of this IP address to add the system properties to the inventory
service.
Point your browser to http://localhost:9081/inventory/systems/[system-ip-address]
by replacing [system-ip-address]
with the IP address you obtained earlier. 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]
.
Testing the microservices
You can test your microservices manually by hitting the endpoints or with automated tests that check your running Docker containers.
Create theSystemEndpointTest
class.system/src/test/java/it/io/openliberty/guides/system/SystemEndpointTest.java
SystemEndpointTest.java
The testGetProperties()
method checks for a 200
response code from the system
service endpoint.
Create theInventoryEndpointTest
class.inventory/src/test/java/it/io/openliberty/guides/inventory/InventoryEndpointTest.java
InventoryEndpointTest.java
The
testEmptyInventory()
method checks that theinventory
service has a total of 0 systems before anything is added to it.The
testHostRegistration()
method checks that thesystem
service was added toinventory
properly.The
testSystemPropertiesMatch()
checks that thesystem
properties match what was added into theinventory
service.The
testUnknownHost()
method checks that an error is raised if an unknown host name is being added into theinventory
service.The
systemServiceIp
variable has the same value as what you retrieved in the previous section when manually adding thesystem
service into theinventory
service. This value of the IP address is 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 IP address determined in the previous section.
mvn verify -Ddockerfile.skip=true -Dsystem.ip=[system-ip-address]
If the tests pass, you 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 and remove your containers:
docker stop inventory system
docker rm inventory system
Great work! You’re done!
You have just built Docker images and run two microservices on Open Liberty in containers.
Guide Attribution
Containerizing microservices by Open Liberty is licensed under CC BY-ND 4.0
Prerequisites:
Nice work! Where to next?
What did you think of this guide?




Thank you for your feedback!
What could make this guide better?
Raise an issue to share feedback
Create a pull request to contribute to this guide
Need help?
Ask a question on Stack Overflow