Devs are from Venus, Ops are from Mars, Containers: Docker Part II

August 24th, 2015 by

In our first article, we started the build of our container using the Dockerfile which was a good beginning. Now it’s time to get our application infrastructure ready for the Docker container, which in this case is Java and Tomcat to be ready to host your Java application goodness.

Java and Tomcat

Let’s continue this example by looking at the “Java” official Dockerfile and then the “Tomcat” image. The Java image starts from a Debian base and installs Java on it and then the Tomcat image starts from the Java image and installs Tomcat. Here is the Java Dockerfile:

FROM buildpack-deps:jessie-scm


# A few problems with compiling Java from source:
# 1. Oracle. Licensing prevents us from redistributing the official JDK.
# 2. Compiling OpenJDK also requires the JDK to be installed, and it gets
# really hairy.

RUN apt-get update && apt-get install -y unzip && rm -rf /var/lib/apt/lists/*


RUN echo 'deb http://httpredir.debian.org/debian jessie-backports main' > /etc/apt/sources.list.d/jessie-backports.list

# Default to UTF-8 file.encoding
ENV LANG C.UTF-8


ENV JAVA_VERSION 8u66
ENV JAVA_DEBIAN_VERSION 8u66-b01-1~bpo8+1

# see https://bugs.debian.org/775775
# and https://github.com/docker-library/java/issues/19#issuecomment-70546872
ENV CA_CERTIFICATES_JAVA_VERSION 20140324

RUN set -x
&& apt-get update
&& apt-get install -y
openjdk-8-jdk="$JAVA_DEBIAN_VERSION"
ca-certificates-java="$CA_CERTIFICATES_JAVA_VERSION"
&& rm -rf /var/lib/apt/lists/*

# see CA_CERTIFICATES_JAVA_VERSION notes above
RUN /var/lib/dpkg/info/ca-certificates-java.postinst configure


# If you're reading this and have any feedback on how this image could be
# improved, please open an issue or a pull request so we can discuss it!

As you can see, most of the Java Dockerfile is comprised of comments. The image is “from” buildpack-deps:jessie-scm. The buildpack-deps is another base image that has several tags, one of which is jessie-scm.

If you have ever used Heroku, build packs are similar to “stack images”, which define the base set of software and versions installed on a container. You can traverse through the jessie-scm tag to the jessie-curl tag to the debian:jessie tag to a “scratch” base image and you will arrive at the following conclusion:

  • The base image is a Debian operating system decompressed on the Docker image using the ADD command
  • The jessie-curl image adds tools to download files to the image: ca-certificates, curl, and wget
  • The jessie-scm image adds source code management tools to the image: bzr, git, mercurial, openssh-client, and subversion

The Java base image starts from a Debian image with curl and wget and contains support for Git, Subversion, and Mercurial. The first thing it does is update the packages on the operating system by running an apt-get command and adds jessie-backports to the source list:

RUN apt-get update && apt-get install -y unzip && rm -rf /var/lib/apt/lists/*
RUN echo 'deb http://httpredir.debian.org/debian jessie-backports main' > /etc/apt/sources.list.d/jessie-backports.list

Next, it sets up Java environment variables and then installs the OpenJDK 8 package:

ENV LANG C.UTF-8
ENV JAVA_VERSION 8u66
ENV JAVA_DEBIAN_VERSION 8u66-b01-1~bpo8+1
RUN set -x
&& apt-get update
&& apt-get install -y
openjdk-8-jdk="$JAVA_DEBIAN_VERSION"
ca-certificates-java="$CA_CERTIFICATES_JAVA_VERSION"
&& rm -rf /var/lib/apt/lists/*

These commands are chained together and include apt-get install, which installs Java. Finally, the Dockerfile executes a script to configure security.

RUN /var/lib/dpkg/info/ca-certificates-java.postinst configure

The Tomcat image is built using the following Dockerfile:

FROM java:7-jre


ENV CATALINA_HOME /usr/local/tomcat
ENV PATH $CATALINA_HOME/bin:$PATH
RUN mkdir -p "$CATALINA_HOME"
WORKDIR $CATALINA_HOME

# see https://www.apache.org/dist/tomcat/tomcat-8/KEYS
RUN gpg --keyserver pool.sks-keyservers.net --recv-keys
05AB33110949707C93A279E3D3EFE6B686867BA6
07E48665A34DCAFAE522E5E6266191C37C037D42
47309207D818FFD8DCD3F83F1931D684307A10A5
541FBE7D8F78B25E055DDEE13C370389288584E7
61B832AC2F1C5A90F0F9B00A1C506407564C17A3
79F7026C690BAA50B92CD8B66A3AD3F4F22C4FED
9BA44C2621385CB966EBA586F72C284D731FABEE
A27677289986DB50844682F8ACB77FC2E86E29AC
A9C5DF4D22E99998D9875A5110C01C5A2F6059E7
DCFD35E0BF8CA7344752DE8B6FB21E8933C60243
F3A04C595DB5B6A5F1ECA43E3B7BBB100D811BBE
F7DA48BB64BCB84ECBA7EE6935CD23C10D498E23

ENV TOMCAT_MAJOR 8
ENV TOMCAT_VERSION 8.0.24
ENV TOMCAT_TGZ_URL https://www.apache.org/dist/tomcat/tomcat-$TOMCAT_MAJOR/v$TOMCAT_VERSION/bin/apache-tomcat-$TOMCAT_VERSION.tar.gz

RUN set -x
&& curl -fSL "$TOMCAT_TGZ_URL" -o tomcat.tar.gz
&& curl -fSL "$TOMCAT_TGZ_URL.asc" -o tomcat.tar.gz.asc
&& gpg --verify tomcat.tar.gz.asc
&& tar -xvf tomcat.tar.gz --strip-components=1
&& rm bin/*.bat
&& rm tomcat.tar.gz*

EXPOSE 8080
CMD ["catalina.sh", "run"]

If you have ever setup a Tomcat instance then this should start looking familiar. The image starts from Java (which we saw starts from a Debian image that contains helper tools) and sets up Tomcat (also called Catalina internally):

FROM java:7-jre

It sets the CATALINA_HOME environment variable to point to “/usr/local/tomcat” and then it prepends the $CATALINA_HOME/bin path to the PATH environment variable. It creates the /usr/local/tomcat directory by executing RUN mkdir and changes directory to /usr/local/tomcat by using the WORKDIR command:

ENV CATALINA_HOME /usr/local/tomcat
ENV PATH $CATALINA_HOME/bin:$PATH
RUN mkdir -p "$CATALINA_HOME"
WORKDIR $CATALINA_HOME

The next half dozen lines setup the environment, download Tomcat using curl, and decompress it using tar.

The last two lines are interesting:

EXPOSE 8080
CMD ["catalina.sh", "run"]

The EXPOSE command tells Docker to make a set of ports available on the image. Tomcat listens on port 8080 by default so this command tells Docker to make port 8080 available on the container. Note that we’ll either pass that port to the host as-is or we might opt to override the container port if we want to.

The CMD command executes a command and its arguments. catalina.sh is the shell script that starts Tomcat and is located in Tomcat’s bin folder (which was added to the PATH earlier in the Dockerfile.) Catalina can start or shutdown Tomcat, so the second parameter passes “run” as an argument to “catalina.sh” to start it.

This discussion has been a long journey to build the following dependency hierarchy:

tomcat

Tomcat extends Java, which extends a BuildPack hierarchy, which ultimately extends a Debian base image. You saw step-by-step how software packages were added to the resultant Docker images, which resulted in a usable application image.

Let’s fire up Tomcat can validate that it works:

$ docker pull tomcat
$ docker run –d –p 8080:8080 tomcat

The docker pull command downloads Tomcat and the docker run executes Tomcat. The parameters passed to docker run are defined as:

  • -d: run this container in detached mode, which means that it won’t shutdown until you stop it; daemon-ize it
  • -p: expose port 8080 from the Docker container to port 8080 on the Docker Engine. We know that the Tomcat image exposes port 8080, so if you want to access Tomcat through port 80 on the Docker Engine then you can change this parameter to –p 80:8080

To verify that Tomcat is running you can find the process:

$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
3ab06c916dee tomcat "catalina.sh run" 2 minutes ago Up About a minute 0.0.0.0:8082->8080/tcp serene_noyce

And let’s see the logging statements that Tomcat is writing:

$ docker logs 3ab
18-Aug-2015 01:42:33.245 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Server version: Apache Tomcat/8.0.24
18-Aug-2015 01:42:33.246 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Server built: Jul 1 2015 20:19:55 UTC

The docker logs command accesses the logs being output for the specified process. Note that we were able to pass the first few characters of the container id, “3ab” instead of the full “3ab06c916dee”, and Docker was smart enough to find it. You can tail (or follow) a log file by adding the –f parameter: docker logs –f 3ab.

In order to access Tomcat, we need to find the IP address of the boot2docker virtual machine upon which the Docker Engine is running. From the Docker shell, find the DOCKER_HOST environment variable (the env command on Mac and the set command on Windows):

$ env | grep DOCKER
DOCKER_HOST=tcp://192.168.99.100:2376

The Docker Engine on my machine is running on 192.168.99.100, so I can open a browser to http://192.168.99.100:8080 to see Tomcat:

When you’re finished, you can stop the Docker Container by executing the following command:

$ docker stop 3ab

You can verify that it has stopped by executing:

$ docker ps

An empty list shows that no running processes.

Conclusion

Containers really are the future of production deployment. Containers allow you to start from a pre-configured and working set of images and rapidly deploy your applications to a production environment. Containers come in many forms, from virtual machines to micro-containers that startup in a matter of seconds.

This article continued a series of articles about Docker that dove deeply into Dockerfiles to see how they are used to construct images. We looked at the hierarchy of Docker images from Tomcat through Java down to Debian to see how one image builds upon another and adds incremental functionality. Throughout the process we saw many of the popular Dockerfile commands and explored how they work and what they do. We also saw many of the command line options used to work with an manipulate the Docker Engine.

The next article will take you through creating your own custom Docker image and publishing it to your DockerHub account. All of this is in preparation for our next major series on containers: publishing Docker images to Amazon’s Elastic Container Service (ECS).

Image source:  Docker logo www.docker.io (Featured Image)

Leave a Reply

Your email address will not be published. Required fields are marked *