Docker tutorial – Build Docker image for your Java application
From the last post,we have understood what is container and why do we use containers in general. Just to recap here are some of the key points
- Containers are an abstraction at the app layer that packages code and dependencies together. Multiple containers can run on the same machine and share the OS kernel with other containers, each running as isolated processes in userspace. Containers take up less space than VMs (container images are typically tens of MBs in size) and start almost instantly.
- A container image is a lightweight, stand-alone, executable package of a piece of software that includes everything needed to run it: code, runtime, system tools, system libraries, settings.
- Containers run apps natively on the host machine’s kernel. They have better performance characteristics than virtual machines that only get virtual access to host resources through a hypervisor. Containers can get native access, each one running in a discrete process, taking no more memory than any other executable.
In this post, we are going to take look at how to build a Docker image for Java application (typically the steps are the same for any type of application).
Quick Snapshot
- Step #1. Setup Docker
- Step #2. Create Dockerfile for our container
- Step #3. Build Docker Image
- Step #4. Test Docker Image
- Step #5. Build Docker container for Maven/Gradle based Java application
- Step #6. Build Docker container for Spring MVC based Java application
- Step #7. Build Docker container for Spring Boot based Java application
- Step #8. Docker Compose Configuration
Step #1. Setup Docker
- From the docker site, install the latest version of the docker for your platform. Docker is available in two editions: Community Edition (CE) and Enterprise Edition (EE). Docker Community Edition (CE) is ideal for developers and small teams looking to get started with Docker and experimenting with container-based apps. Docker Enterprise Edition (EE) is designed for enterprise development and IT teams who build, ship, and run business-critical applications in production at scale.
- Once the installation of docker is over, check the installation by running following command docker run hello-world:
- Run docker –version to check the version of the docker you’re running.
OK, now we have got the docker setup,next step is to define the docker container.
Step #2. Create Dockerfile for our container
- Dockerfile will define what goes on in the environment inside the container. Access to resources like networking interfaces and disk drives is virtualized inside this environment, which is isolated from the rest of the system, so you have to map ports to the outside world, and be specific about what files you want to “copy in” to that environment. So that you can expect that the build of your app defined in this Dockerfile will behave exactly the same wherever it runs.
- Common Dockerfile instructions start with RUN, ENV, FROM, MAINTAINER, ADD, and CMD, among others.
- FROM – Specifies the base image that the Dockerfile will use to build a new image. As an example, we are going to use phusion/baseimage as our base image (this is minimal Ubuntu-based image)
- MAINTAINER – Specifies the Dockerfile Author Name and his/her email.
- RUN – Runs any UNIX command to build the image.
- ENV – Sets the environment variables. For this post, JAVA_HOME is the variable that is set.
- CMD – provides the facility to run commands at the start of the container. This can be overridden upon executing the docker run command.
- ADD – This instruction copies the new files, directories into the Docker container file system at a specified destination.
- EXPOSE – This instruction exposes a specified port to the host machine.
- To begin with, create a new folder and then create a file in it named
“Dockerfile”
with the following content.# Dockerfile FROM phusion/baseimage:0.9.17 MAINTAINER Author Name [email protected]
- Once we have the baseimage set,next step is to update the base image with Ubuntu repository by running apt-get -y update command
RUN echo "deb http://archive.ubuntu.com/ubuntu trusty main universe" > /etc/apt/sources.list RUN apt-get -y update
- If you want to use python-software-properties, this enables us to use add-apt-repository for use later in the process.
RUN DEBIAN_FRONTEND=noninteractive apt-get install -y -q python-software-properties software-properties-common
- Install Oracle Java 8
ENV JAVA_VER 8 ENV JAVA_HOME /usr/lib/jvm/java-8-oracle RUN echo 'deb http://ppa.launchpad.net/webupd8team/java/ubuntu trusty main' >> /etc/apt/sources.list && \ echo 'deb-src http://ppa.launchpad.net/webupd8team/java/ubuntu trusty main' >> /etc/apt/sources.list && \ apt-key adv --keyserver keyserver.ubuntu.com --recv-keys C2518248EEA14886 && \ apt-get update && \ echo oracle-java${JAVA_VER}-installer shared/accepted-oracle-license-v1-1 select true | sudo /usr/bin/debconf-set-selections && \ apt-get install -y --force-yes --no-install-recommends oracle-java${JAVA_VER}-installer oracle-java${JAVA_VER}-set-default && \ apt-get clean && \ rm -rf /var/cache/oracle-jdk${JAVA_VER}-installer
- Set Oracle Java as the default Java
RUN update-java-alternatives -s java-8-oracle RUN echo "export JAVA_HOME=/usr/lib/jvm/java-8-oracle" >> ~/.bashrc
- Clean Up when finished
RUN apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
- Finally, add command for docker’s init system
CMD ["/sbin/my_init"]
- Docker file would now be looking like the one below
# Dockerfile FROM phusion/baseimage:0.9.17 MAINTAINER Author Name <[email protected]> RUN echo "deb http://archive.ubuntu.com/ubuntu trusty main universe" > /etc/apt/sources.list RUN apt-get -y update RUN DEBIAN_FRONTEND=noninteractive apt-get install -y -q python-software-properties software-properties-common ENV JAVA_VER 8 ENV JAVA_HOME /usr/lib/jvm/java-8-oracle RUN echo 'deb http://ppa.launchpad.net/webupd8team/java/ubuntu trusty main' >> /etc/apt/sources.list && \ echo 'deb-src http://ppa.launchpad.net/webupd8team/java/ubuntu trusty main' >> /etc/apt/sources.list && \ apt-key adv --keyserver keyserver.ubuntu.com --recv-keys C2518248EEA14886 && \ apt-get update && \ echo oracle-java${JAVA_VER}-installer shared/accepted-oracle-license-v1-1 select true | sudo /usr/bin/debconf-set-selections && \ apt-get install -y --force-yes --no-install-recommends oracle-java${JAVA_VER}-installer oracle-java${JAVA_VER}-set-default && \ apt-get clean && \ rm -rf /var/cache/oracle-jdk${JAVA_VER}-installer RUN update-java-alternatives -s java-8-oracle RUN echo "export JAVA_HOME=/usr/lib/jvm/java-8-oracle" >> ~/.bashrc RUN apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* CMD ["/sbin/my_init"]
Step #3. Build Docker Image
Now that we have completed Dockerfile, next step is to build Docker image by docker build command
docker build -t demo/oracle-java:8 .
Here -t specifies the name of the image. The name demo/oracle-java, and the 8 after the colon, specify the image tag. The tag 8 is used because we are using Java version 8.
Image – Docker build complete
Step #4. Test Docker Image
- To test the image, we have built using docker build command, create a simple java program to print “Hello World”:
public class Main { public static void main(String[] args) { System.out.println("Hello, World"); } }
- Execute the following commands to compile the Java file from the current project directory.
docker run --rm -v $PWD:/app -w /app demo/oracle-java:8 javac Main.java
- Execute the compiled Main.class file.
docker run --rm -v $PWD:/app -w /app demo/oracle-java:8 java Main
Now you should get Hello, World
displayed on your terminal.
Step #5. Build Docker container for Maven/Gradle based Java application
In the real world scenario, Java apps would need other applications like Maven/Gradle or Spring MVC on top of the base image. Following is an example of how to add more applications on top of the base image.
- Now let us consider, we are going to use Gradle as a build tool, let’s start building the container image from the base.
FROM demo/oracle-java:8 RUN wget -q https://services.gradle.org/distributions/gradle-3.3-bin.zip \ && unzip gradle-3.3-bin.zip -d /opt \ && rm gradle-3.3-bin.zip ENV GRADLE_HOME /opt/gradle-3.3 ENV PATH $PATH:/opt/gradle-3.3/bin
If you notice, the base image is a
demo/oracle-jdk:8
image we have built this image in our previous example. There is also official OpenJDK 8 available on the Docker repository. - Build the docker image from the above Dockerfile using this command
$ docker build -t demo/gradle:3.3-jdk-8 .
- Now let’s test the Gradle image using the following command
$ docker run --rm -v "$PWD":/usr/src/project -w /usr/src/project gradle gradle init --type=java-library
This command should start the Gradle daemon & create the directory structure shown below
Starting a Gradle Daemon (subsequent builds will be faster) :wrapper :init
Step #6. Build Docker container for Spring MVC based Java application
For Spring MVC based applications, we would require Tomcat or any other web server. To create a new image, we can use the base image as a Gradle image and install Tomcat on it to run the web application.
- Create Dockerfile like the one below
# Dockerfile FROM demo/maven:3.3-jdk-8 MAINTAINER Author <[email protected]> RUN apt-get update && \ apt-get install -yq --no-install-recommends wget pwgen ca-certificates && \ apt-get clean && \ rm -rf /var/lib/apt/lists/* ENV TOMCAT_MAJOR_VERSION 8 ENV TOMCAT_MINOR_VERSION 8.0.11 ENV CATALINA_HOME /tomcat
- Add commands to install Tomcat
RUN wget -q https://archive.apache.org/dist/tomcat/tomcat-${TOMCAT_MAJOR_VERSION}/v${TOMCAT_MINOR_VERSION}/bin/apache-tomcat-${TOMCAT_MINOR_VERSION}.tar.gz && \ wget -qO- https://archive.apache.org/dist/tomcat/tomcat-${TOMCAT_MAJOR_VERSION}/v${TOMCAT_MINOR_VERSION}/bin/apache-tomcat-${TOMCAT_MINOR_VERSION}.tar.gz.md5 | md5sum -c - && \ tar zxf apache-tomcat-*.tar.gz && \ rm apache-tomcat-*.tar.gz && \ mv apache-tomcat* tomcat ADD create_tomcat_admin_user.sh /create_tomcat_admin_user.sh RUN mkdir /etc/service/tomcat ADD run.sh /etc/service/tomcat/run RUN chmod +x /*.sh RUN chmod +x /etc/service/tomcat/run EXPOSE 8080
- Finally, add command to use baseimage-docker’s init system
CMD ["/sbin/my_init"]
- Create the file to check if Tomcat admin user is created this file would be used in the next step.
!/bin/bash if [ -f /.tomcat_admin_created ]; then echo "Tomcat 'admin' user already created" exit 0 fi
- Add the following script to generate a password.
PASS=${TOMCAT_PASS:-$(pwgen -s 12 1)} _word=$( [ ${TOMCAT_PASS} ] && echo "preset" || echo "random" ) echo "=> Creating an admin user with a ${_word} password in Tomcat" sed -i -r 's/<\/tomcat-users>//' ${CATALINA_HOME}/conf/tomcat-users.xml echo '' >> ${CATALINA_HOME}/conf/tomcat-users.xml echo '' >> ${CATALINA_HOME}/conf/tomcat-users.xml echo '' >> ${CATALINA_HOME}/conf/tomcat-users.xml echo '' >> ${CATALINA_HOME}/conf/tomcat-users.xml echo '' >> ${CATALINA_HOME}/conf/tomcat-users.xml echo "" >> ${CATALINA_HOME}/conf/tomcat-users.xml echo '' >> ${CATALINA_HOME}/conf/tomcat-users.xml echo "=> Done!" touch /.tomcat_admin_created echo "========================================================================" echo "You can now configure to this Tomcat server using:" echo "" echo " admin:${PASS}" echo "" echo "========================================================================" This file creates the Tomcat admin user. Add one more file in the same directory named as run.sh with following content. This will call the create users file and then reload the Tomcat server. !/bin/bash if [ ! -f /.tomcat_admin_created ]; then /create_tomcat_admin_user.sh fi exec ${CATALINA_HOME}/bin/catalina.sh run
- Build the docker image using the following command.
$ docker build -t demo/spring:maven-3.3-jdk-8 .
Congrats! You’ve successfully built container for your Java application.
Step #7. Build Docker container for Spring Boot based Java application
For Spring Boot based applications, we just have to use the generated jar artifacts. We would be using openjdk
as the base image and required jars.
FROM openjdk:8-jre WORKDIR / #add required jars ADD spring-boot-rest-postgresql-0.0.1-SNAPSHOT.jar spring-boot-rest-postgresql-0.0.1-SNAPSHOT.jar #expose port EXPOSE 8080 #cmd to execute ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/spring-boot-rest-postgresql-0.0.1-SNAPSHOT.jar"]
Step #8. Docker Compose Configuration
If you’re looking for running multi-container applications using Docker Compose tool then the configuration is as easy, there would be YAML file to configure your application’s services/networks/volumes, etc., Then, with a single command, you can create and start all the services from the compose configuration.
Here are the key steps :
- Define
Dockerfile
for your container(s). - Define
docker-compose.yml
for the services that make up your application services. - Run
docker-compose up
and Compose starts and runs your entire app.
Sample docker-compose.yml
for SpringBoot API application would look like this:
version: '3' services: api: build: context: . dockerfile: apiDockerfile ports: - '8080:8080' depends_on: - postgresdb networks: - samplenet networks: samplenet: null
To scale services using Docker compose refer here. There is much more to the Docker platform than what was covered here, but now you would have got a good idea of the basics of building containers for an application.
Like this post? Don’t forget to share it!
Additional Resources:
- Official documentation as a reference to understand any command.
- Docker build reference.
- Docker run reference.
- Take a free course on Building Scalable Java Microservices with Spring Boot and Spring Cloud
- TOP 6 GUI tools for managing Docker environments
- Best Practices article on writing Docker files.
- Test your knowledge on Dockerfile.
Average Rating