When I first learned about Docker, I thought of Bitnami and how it seemed a natural fit for them to offer Docker image versions of their stacks. It turns out that they are in the process of doing exactly that. However, at the time of this writing, they don't have these available, so I decided to build my own. What follows is a step by step recipe for taking the Bitnami Tomcat 7 installer and building a Docker image that captures the result of a successful install.
Step 0 - Create a VM and install Docker
Step 1 - Download the Bitnami Tomcat Installer onto Your VM
Also note the I've saved the installer under a new directory (which we will reference in Step 3) and made it executable.
Step 2 - Download/Pull the Base Docker Image
Step 3 - Start a Container
--cap-add=ALL: When it starts, Tomcat tries to set some capabilities (i.e. establish the privilege to do one or more "superuser like" things). By default Docker does not allow processes within a container to do this. This option allows processes within the container to set any capability they want. This is a sloppy and dangerous thing to do. I should dig into the Tomcat code and figure out exactly which capabilities it is requesting and grant only those capabilities (see the "principle of least privilege").
-v /root/bitnami:/bitnami: This option bind mounts "/root/bitnami" on the VM to "/bitnami" in the container. This will allow us to access the installer file from inside the container.
-p 80:80: By default the Apache web server listens on port 80. This option maps port 80 of the container to port 80 on our VM. Obviously you can map the container port to any free port on your VM (e.g 8080 using "-p 8080:80").
-i, -t: These two options connect you to the shell running inside the container.
ubuntu: This option specifies the image to run in the container. In this case it is the default Ubuntu image that we pulled in Step 2.
/bin/bash: This option tells Docker to run a bash shell inside the container.
At this point you should find yourself at a container-level prompt like:
Step 4 - Run the Bitnami Installer
Step 5 - Snapshot the Container
After this completes you can simply exit the bash shell to exit the container and return to your VM-level shell. At this point we can snapshot the container and create a new image using the "docker commit" command like so:
Step 6 - Launching the Image
Step 7 - Stopping the Container
Steps 3-5 could have been replaced using the "docker build" command and a Docker file. However, at the time of this writing, the containers used during the "docker build" command do not allow their processes to request capabilities. A
command will fail with the following error:
set_caps: failed to set capabilities
check that your kernel supports capabilities
set_caps(CAPS) failed for user 'tomcat'
Service exit with a return value of 4
when Tomcat tries to run for the first time. This issue is being tracked by Docker here: https://github.com/docker/docker/issues/1916.
Why Use Docker At All?
At the beginning of this post I pointed out that Bitnami stacks exist in machine image form for most popular systems. I can go to AWS and, in less time and less effort, create a new VM that is functionally equivalent to the docker container that I have created here. Some points:
- My Bitnami Tomcat stack Docker image is a just a building block. Next I'm going to install a webapp on Tomcat, a database on MySQL, etc. Then I'm going to snapshot that. Again, I could do the same with AWS, but I can't run an AMI anywhere besides AWS. I can take my Docker images and run them on anything with a compatible kernel.
- When saved as a TAR file my docker image is approximately 800 Mb. Most VM images are far larger than this. Lighter is faster.
- Bitnami does a great job with integration but nothing is ever quite exactly the way you want it. The dink-->test-->dink-some-more cycle in Steps 3 and 4 is much faster using containers on an individual VM than using multiples VMs.
- If, for whatever reason, I wanted to run multiple instances/versions of my stack it would probably be much cheaper to run them side-by-side in separate containers on the same (larger) VM than it would be to run them each in their own (smaller) VMs. This cost difference is even greater if I decide that I need to make my stacks available at a static IP address and/or given DNS name.