Docker Engine is an all-in-one build platform for packaging, porting, and running containerized distributed applications. Docker Engine is similar to a virtual machine in that it runs on an OS kernel, but Docker is more different than similar to a virtual machine. Although a virtual machine encapsulates a whole of the guest OS kernel in addition to the application binaries and libraries, multiple Docker containers run in isolation on a single underlying OS kernel with each container having a lightweight encapsulation of the kernel with its own file system and networking. Another difference is that even though a virtual machine requires a hypervisor such as VirtualBox, which runs on the underlying OS with the guest OS running on top of the hypervisor, Docker Engine runs directly on the underlying OS Kernel. A Docker container includes the application binaries and all the required dependencies.
Figure 1: Docker containers
A Docker container may be created and run using a Docker image, which is built from a Dockerfile. Software binaries are downloaded using a Docker image and a Docker container is created from the Docker image. A Dockerfile has a set of instructions to download software binaries, set environment variables, and run commands. A Dockerfile could inherit an instruction set from another Docker image using the FROM instruction. Most Docker images, including the openjdk image for Java, are based on a Linux OS Docker image. An interactive shell may be started on a Docker container that is created from a Docker image based on a Linux OS image and Linux commands run in the interactive shell.
In this tutorial, we shall run Java on Docker Engine and develop a Hello World Java application. We shall use the official Docker image for Java, the openjdk image. We shall discuss two methods to run Java in a Docker container:
- Create a Docker container directly from the openjdk image, and
- Create a Docker image based on the openjdk image for a Hello World Java application and create a Docker container from the Docker image for the Hello World application.
This tutorial has the following sections/sub-sections.
- Setting the Environment
- Pulling a Docker Image for Java
- Using the openjdk Docker Image Directly
- Running a Docker Container and Starting an Interactive Shell
- Copying a Java Application to the Docker Container
- Compiling and Running the Java Application
- Creating a Docker Image for a Hello World Java Application
- Creating a Dockerfile for a Java Application
- Creating the Docker Image
- Running the Dockerized Hello World Application
- Starting an Interactive Shell on a Hello World Docker Container
- Running the Java Application in the Interactive Shell
- Running a Docker Container in Detached Mode for the Dockerized Hello World Application
- Listing the Logs
- Listing the Docker Images
- Removing a Docker Container
- Removing a Docker Image
Setting the Environment
Create an AWS EC2 instance from a CoreOS AMI. CoreOS is an OS designed for containers and Docker Engine is pre-installed on the OS. Another OS that supports Docker (https://docs.docker.com/engine/installation/) could also be used instead, but Docker would need to be installed on the OS. To create a CoreOS based AWS EC2 instance, refer to https://coreos.com/os/docs/latest/booting-on-ec2.html.
SSH login to the CoreOS instance and Docker Engine commands may be invoked directly with the “docker” command.
Figure 2: Running the “docker” command
Pulling a Docker Image for Java
A Docker image should either be available on the local machine or from another repository such as Docker Hub to run a Docker container application. We shall be using the official Docker image openjdk available from the Docker Hub. A Docker container is run using the docker run command. A Docker image gets downloaded if not already not downloaded previously when the docker run command is run. Alternatively, a Docker image could be downloaded using the docker pull command. Download the openjdk Docker image using the docker pull command.
docker pull openjdk
Docker images are versioned using tags. The openjdk:latest image gets downloaded, which is the latest image for the openjdk image.
Figure 3: The latest image
Subsequently, list the Docker images available on the local machine.
docker images
The openjdk image should get listed in addition to other images that may have been downloaded previously.
Figure 4: The openjdk image
Using the openjdk Docker Image Directly
Next, we shall use the openjdk image to run a Hello World Java application. A Docker container may be run in one of two modes: background or foreground. The default mode is for a Docker container to run in the foreground. When run in the foreground, the Docker container process’s standard input, output, and error streams are attached to the console and output from the Docker container’s process may be viewed in the console. An interactive pseudo-tty may be started to interact with the application running in the Docker container. When run in the background with the -d option, the Docker container is not interacting with the command line from which the docker run command is run. An interactive pseudo-tty terminal may be started to connect to the Docker container’s process subsequently. The Docker container started in detached mode exits when the process used to start the Docker container exits.
Running a Docker Container and starting an Interactive Psuedo-TTY
To run a Docker container in the foreground and start a pseudo-tty, another pseudo-tty must not already be running on the command line console. Exit a tty if already running with the exit command. Start a Docker container with the docker run command and provide the -it options to keep STDIN open and start a psuedo-tty.
sudo docker run -it openjdk bash
A pseudo-tty gets started, from which application commands may be run to interact with the application running in the container.
Figure 5: Starting the pseudo-tty
Because the openjdk Docker image is for Java, all the standard Java commands such as java and javac are available. Output the Java version with the java -version command.
Figure 6: Output of the java -version command
Copying a Java Application to the Docker Container
We shall use a Hello World Java application in the tutorial. Copy the following listing to a file entitled HelloWorldApp.java.
class HelloWorldApp { public static void main(String[] args) { // Display the string. System.out.println("Hello World!"); } }
A pseudo-tty on a Docker container’s process does not directly support vi editor commands and a file cannot be created directly in the container. A file could be copied to the container using the docker cp command. To copy a command from the local machine to a Docker container, the command syntax is docker cp [OPTIONS] SRC_PATH|- CONTAINER:DEST_PATH. Obtain the Docker container ID from the tty command prompt.
Figure 7: Obtaining the Docker container ID
Run the following command from the CoreOS command prompt to copy the Java application.
docker cp HelloWorldApp.java c4393794390b: HelloWorldApp.java HelloWorldApp.java
The Java application gets copied to the Docker container.
Figure 8: Copying the Java application to the Docker container
List the files in the Docker container from the tty with the ls -l command and the HelloWorldApp.java should be listed.
Figure 9: Listing the files in the Docker container
Compiling and Running the Java Application
Now that you have copied the Java application to the Docker container, compile the Java application.
javac HelloWorldApp.java
The application binaries file HelloWorldApp.class should get generated.
Figure 10: Generating the application binaries file
Run the HelloWorld application.
java HelloWorldApp
The output from the application gets generated.
Figure 11: Generating output from the application
Creating a Docker Image for a Hello World Java Application
The preceding discussion is one method to run a Java application on Docker Engine which is t first copy the application to a Docker container and subsequently run the application. But, if an application is much more bulky than a simple Hello World application a better option is to create a Docker image from the application and run a Docker container for the Docker image. As the application binaries are packaged in the Docker container copying of files to the Docker container is precluded. Next, we shall discuss creating a Docker image for the Hello World Java application and running the application in a container.
Creating a Dockerfile for a Java Application
A Dockerfile is a set of instructions or directives that could be used to build a Docker image. The Docker image would include all the application binaries including dependencies, and environment variable settings required to run a Docker container for the application.
First, set up the directory and file structure from which the Docker image is to be built. Create a directory (helloworld for example), set directory permissions to global (777), and change directory (cd) to the directory.
sudo mkdir helloworld sudo chmod 777 helloworld cd helloworld
The directory structure should be similar to the following; the directory name helloworld is arbitrary.
Figure 12: Viewing the directory structure
Create a Java application file HelloWorldApp.java.
Figure 13: Creating the Java application file
Create a file called Dockerfile and copy the following listing to the file.
FROM openjdk:7 COPY . helloworld WORKDIR helloworld RUN javac HelloWorldApp.java CMD ["java", "HelloWorldApp"]
The directives in the Dockerfile are as follows.
Directive | Discussion |
FROM | The base Docker image from which the new Docker image is to be built is set to opendjk |
COPY | The syntax is COPY <src>… <dest> and the directive copies the files and directories at the src path to the dest path within a Docker container’s file system. |
WORKDIR | Sets the working directory for any RUN, CMD, ENTRYPOINT, COPY, and ADD directives subsequent in the Dockerfile. |
RUN | Runs a command. In the example Dockerfilem the javac command is run to compile the Java application. |
CMD | Provides the defaults for a Docker container. The example Dockerfile runs the java command on the HelloWorldApp class. |
The Dockerfile is shown in the vi editor.
Figure 14: Viewing the Dockerfile in the vi editor
The file and directory structure should be as follows, with the helloworld directory as empty.
Figure 15: Viewing the file and directory structure
Creating the Docker Image
The docker build command is used to build a Docker image using the instructions in the Dockerfile. The syntax to run docker build is as follows.
Figure 16: The syntax to run docker build
For a complete set of options available to the docker build command, run the docker build -help command.
Figure 17: Running the docker build -help command
Create a Docker image called “hello-world-app” from the source code at path “.”, which is the current directory, using the Dockerfile.
sudo docker build -f Dockerfile -t hello-world-app .
The Docker image hello-world-app gets built using the instructions in the Dockerfile.
Figure 18: Building the Docker image
Running the Dockerized Hello World Application
The docker run command is used to run a Docker container for a Docker image. The following command runs a Docker container called hello-world-app (–name) from the Docker image hello-world-app and starts an interactive pseudo-tty (-it) on the container, and removes the container after it exits (–rm).
sudo docker run -it --rm --name hello-world-app hello-world-app
The Docker container for the Dockerized Hello World Application runs to output a message.
Figure 19: Running the Hello World Application
Starting an Interactive Shell on a Hello World Docker Container
As an alternative to running a Docker container, we could start an interactive tty on the Docker container, which shall provide access to the binaries packaged in the Docker container’s file system. The following command starts an interactive bash shell on a Docker container:
sudo docker run -it hello-world-app bash
List the Docker container’s file system with ls -l and the HelloWorldApp binaries and application should get listed.
Figure 20: Listing the Docker container’s file system
Running the Java Application in the Interactive Shell
The pseudo-tty provides access the same application commands as available to an openjdk Docker image container because the hello-world-app Docker image is built from the openjdk image. The java command may be run just as from a Linux OS on which Java has been installed.
Figure 21: The Docker image
Run the HelloWorldApp with the following command.
java HelloWorldApp
The output message from the Java application gets generated.
Figure 22: Generating the output message
Running a Docker Container in Detached Mode for the Dockerized Hello World Application
Previously, we ran the Docker container in the foreground and started an interactive tty on the container. But, a Docker container could also be run in detached mode with the -d option. The following command runs a Docker container called hello-world for Docker image hello-world-app in the background.
sudo docker run -d --name hello-world hello-world-app
A Docker container gets started but no output is generated on the console because the container process’s standard output is not attached to the console.
Figure 23: No output is generated
Listing the Logs
But, the Docker container runs nevertheless and generates an output that may be obtained from the logs for the container. The docker logs command is used to output a container’s logs. To get the logs for the hello-world container, run the following command:
docker logs hello-world
The message generated by the Docker container is output.
Figure 24: Generating the output
Listing the Docker Images
The Docker images may be listed with the following command:
docker images
All the Docker images on the local machine get listed. The openjdk Docker image is listed twice with different tags, latest and 7.
Figure 25: The Docker image is listed twice
Removing a Docker Container
All Docker containers, running and exited, may be listed with the following command:
docker ps -a
All Docker containers get listed. When not assigned a name explicitly, Docker containers get some default name generated by the Docker Engine. The exited containers have STATUS “Exited”.
Figure 26: The Docker containers are listed
A Docker container may be removed with the docker rm command.
Figure 27: Issuing the docker rm command
A Docker container must be stopped before removing the container. To remove all stopped containers, run the following command:
sudo docker rm $(sudo docker ps -a -q)
All stopped containers get removed.
Figure 28: The stopped containers are removed
To remove only exited containers, the command is different.
sudo docker rm -v $(sudo docker ps -a -q -f status=exited)
Removing a Docker Image
The docker rmi command is used to remove a Docker image. Remove the openjdk image with the following command:
docker rmi openjdk
The openjdk:latest Docker image gets removed. The tag “latest” is used if none is specified explicitly.
Figure 29: The latest Docker image is removed
Similarly, remove the hello-world-app Docker image, which we created using a Docker file.
Figure 30: Removing the hello-world-app Docker image
Listing the Docker images subsequently does not list the removed Docker images. A Docker image may be removed only if no Docker container using the image is running. At times, a Docker image may not get downloaded properly and a dangling image with REPOSITORY and TAG <none> gets listed.
Figure 31: Listing a dangling image
The dangling Docker images may be removed with the following command:
sudo docker rmi $(docker images -f "dangling=true" -q)
All dangling Docker images get removed.
Figure 32: Removing all dangling Docker images
Listing the Docker images subsequently does not list the dangling images.
Figure 33: The dangling images are no longer listed
In this tutorial, we discussed using Java on Docker Engine with a Hello World application.