Using Docker to efficiently create multiple tomcat instances
In my previous blog article I gave a short introduction into Docker (“an open-source engine that automates the deployment of any application as a lightweight, portable, self-sufficient container that will run virtually anywhere”). In this article we’ll check out how to create an image for Tomcat 7 and the Java 7 JDK as dependency.
So, let’s go ahead and do some ‘coding’. First, you need to install docker. Instructions can be found here. I already mentioned you need a Linux system with a modern kernel, so if you happen to be a Mac or Windows user, there are instructions on linked pages on how to use Vagrant to easily setup a virtual machine (VM) to use. For now we’ll work locally, but once you start installing servers you might find the Chef project to install docker useful as well.
As a first step after installation, let’s pick the first example from the Docker getting started page and create an Ubuntu 12.04 container, with completely separated processes, its own file system and its own network interface (but with network connection via the host), and have it print “hello world”. Do this by running
docker run ubuntu /bin/echo hello world
Cool huh? You probably just ran something on a different OS than that of your own machine or (in case you’re on Windows/Mac) the VM in which Docker is running! In this command ubuntu
defines the image (found automatically as it is one of the standard images supplied by Docker). The run
command creates an instance of the image (a container), feeding it /bin/echo hello world
as the command to execute.
The behavior that you just tested is exactly the behavior that I had, or some other fellow doing this blog; no more “but it works on my machine…”. It probably took some time due to Ubuntu 12.04 being downloaded. However, that is on the first run only; after that we’re talking milliseconds. You can run
docker images
to see that the Ubuntu 12.04 has been downloaded to your system, aliased as precise
and latest
. So, you could have used docker run ubuntu:precise /bin/echo hello world
(notice the use of the tag) as well. Run
docker ps -a -s
to see the containers. -a
makes sure you see already closed containers (useful, since echoing hello world doesn’t take that long), and -s
gives you the container file size as well. Containers as simple as these are some kBs big only (even when running multiple concurrently), because they both use the same image and share those resources. That is a major advantage over VMs, but I already mentioned that in the introductory blog.
Step 2. Create an image with the Java JDK 7. I chose to do the Oracle version with the added complexity of scripting the automatic license acceptance. Actually, we could go about this two ways.
- Run the Ubuntu image interactively (using
docker run ubuntu ps -i -t /bin/bash
) and do whatever we want in the shell. When done, we could exit the shell, thereby closing the container, and commit the result into a new image usingdocker commit quintenk/jdk7-oracle
. This method has the disadvantage of not being transparant or reproduceable. - Use Dockers Dockerfile Builder: we create a simple script file called “Dockerfile” and have Docker run and commit each command to a new image, allowing us to give a human readable name to the final result. Note that while we end up with 8 images (which you can see later by using the
-a
option with thedocker images
command), Docker only saves a diff for each file, so most images use a few kBs of disk space. Now all build steps are completely transparant and reproduceable. You can also find the following Dockerfiles on my github repository.
FROM ubuntu:precise MAINTAINER Quinten Krijger < qkrijger [at] gmail {dot} com> # make sure the package repository is up to date RUN echo "deb http://archive.ubuntu.com/ubuntu precise main universe" > /etc/apt/sources.list RUN apt-get update && apt-get -y install python-software-properties RUN add-apt-repository ppa:webupd8team/java RUN apt-get update && apt-get -y upgrade # automatically accept oracle license RUN echo oracle-java7-installer shared/accepted-oracle-license-v1-1 select true | /usr/bin/debconf-set-selections # and install java 7 oracle jdk RUN apt-get -y install oracle-java7-installer && apt-get clean RUN update-alternatives --display java RUN echo "JAVA_HOME=/usr/lib/jvm/java-7-oracle" >> /etc/environment
The FROM
statement is the mandatory first line to let Docker know what to build this image from. MAINTAINER
is of course optional. The RUN
commands are executed when building the image: for each line, a container is initialized and the command executed after which the result is committed. To actually build the image, you need this in a file called “Dockerfile” (e.g. by cloning my repository) and go to the file location. Then run
docker build -t quintenk/jdk7-oracle .
Notice that Ubuntu was not downloaded again. As an experiment to see how developing a Dockerfile goes in practice you could alter the Dockerfile and change one of the latter lines. If you then build again (maybe without the -t quintenk/jdk7-oracle
option) you will find that the caching mechanism is worth gold. Try stuff like that when testing Ansible installation scripts on a local VM and you’ll see the difference!
Step 3. Now that we have an image with Java installed, we can create other images based on it. We’ll try Tomcat 7, but if you make other images as well on the same base, they will share the JDK image resources! For Tomcat I created the following Dockerfile (also in the same git repository):
FROM quintenk/jdk7-oracle MAINTAINER Quinten Krijger "qkrijger@gmail.com" RUN apt-get -y install tomcat7 RUN echo "JAVA_HOME=/usr/lib/jvm/java-7-oracle" >> /etc/default/tomcat7 EXPOSE 8080 CMD service tomcat7 start && tail -f /var/lib/tomcat7/logs/catalina.out
(edit: note the comment by Anton on using ‘tail -F’ rather than ‘-f’) You’ll notice the EXPORT
command in here, which exposes Tomcats port to the container. The CMD
is the command that will be executed when a container is started. So, when we build the image and run it as a daemon using
docker run -d quintenk/tomcat7
it starts Tomcat as a service and then tails the log file. The last step is needed because you need a running foreground process in order for the container to keep running. If you omit it, the container will start Tomcat and shut down immediately after. Now run docker ps
to see
ID d02ed25688be IMAGE quintenk/tomcat7:latest COMMAND /bin/sh -c service t PORTS 49153 -> 8080
So, 8080 on the container is exposed to the host port 49153, and you can see Tomcat running at http://localhost:49153. Run another container, and you’ll probably get it under port 49154. This way, we can now start lots of Tomcat containers, all completely isolated and show the results in the same browser.
As a next step, and an actual use case, I would install different versions of the same web application, create images from that and run them at the same time. You can then demonstrate to your customer what you have produced in the last few weeks :). However, I’ve blogged enough for today. If you do give it a spin, you don’t have to expose 8080 again, but I believe you do need to give a new CMD
. If you don’t specify a CMD
you could run the container in interactive mode with shell, and start tomcat from the command line – maybe less clean, but useful to understand that it is an option.
Note that if you would just like the images and aren’t interested in building them yourself, you can download them from the Docker index, where I published them. To do this, simply run
docker pull quintenk/tomcat7
From there, you could install you own web application using the techniques described in this article.
Have fun!