Follow along with this article as we take a guided tour of containerizing Zookeeper using Docker. This guide will show how to install Zookeeper to the container, how to configure the Zookeeper application, and how to share data volumes between the host and container.

This tutorial makes use of a Dockerfile for specifying our container’s contents. If you need more information on writing a Dockerfile, refer to the official documentation.

Version 1 - Specifying a Base Image

A Docker container is built off of a base Linux image. These images provide core functionality for your container and are specified using the FROM command. FROM allows you to specify both an image and a tag with the tag being a version of the image. In the following Dockerfile, we are building a container using the jessie version of the debian image.

FROM debian:jessie

This is enough to build your Docker image. You can exercise it by running a container in interactive mode and starting a bash prompt. When you are done, type exit at the container’s prompt to return to your host machine.

> docker build -t sookocheff/docker-zookeeper:1  .
...
> docker run -it sookocheff/docker-zookeeper:1 /bin/bash
...
> exit

Version 2 - Installing Zookeeper

Now that we have a base image, we can install Zookeeper on it using the RUN command. RUN allows you to execute arbitrary commands on the image. In this example, we install Zookeeper version 3.4.7 to /opt/zookeeper.

FROM debian:jesse

RUN apt-get update && apt-get install -y openjdk-7-jre-headless wget
RUN wget -q -O - http://mirror.csclub.uwaterloo.ca/apache/zookeeper/zookeeper-3.4.7/zookeeper-3.4.7.tar.gz
RUN tar -xzf - -C /opt
RUN mv /opt/zookeeper-3.4.7 /opt/zookeeper \

Each command executed within a Dockerfile creates an additional image layer. Each layer replicates the contents of the layers before it and so each layer ultimately increases the size of your Docker image. It is therefore considered best practice to minimize the number of layers by combining statements in your Dockerfile. You will need to balance readability with performance and adjust your Dockerfile to your needs.

FROM debian:jesse

RUN apt-get update && apt-get install -y openjdk-7-jre-headless wget
RUN wget -q -O - http://mirror.csclub.uwaterloo.ca/apache/zookeeper/zookeeper-3.4.7/zookeeper-3.4.7.tar.gz | tar -xzf - -C /opt \
    && mv /opt/zookeeper-3.4.7 /opt/zookeeper \
    && cp /opt/zookeeper/conf/zoo_sample.cfg /opt/zookeeper/conf/zoo.cfg

Version 3 - Expose Environment Variables

Java applications often times require the JAVA_HOME environment variable to be set. The ENV keyword creates environment variables in your image.

FROM debian:jesse

RUN apt-get update && apt-get install -y openjdk-7-jre-headless wget
RUN wget -q -O - http://mirror.csclub.uwaterloo.ca/apache/zookeeper/zookeeper-3.4.7/zookeeper-3.4.7.tar.gz | tar -xzf - -C /opt \
    && mv /opt/zookeeper-3.4.7 /opt/zookeeper \
    && cp /opt/zookeeper/conf/zoo_sample.cfg /opt/zookeeper/conf/zoo.cfg

ENV JAVA_HOME /usr/lib/jvm/java-7-openjdk-amd64

Version 4 - Expose Ports

Zookeeper requires several ports to be open to communicate with other Zookeeper nodes and with clients. You can tell the container to listen to network ports using the EXPOSE keyword.

FROM debian:jesse

RUN apt-get update && apt-get install -y openjdk-7-jre-headless wget
RUN wget -q -O - http://mirror.csclub.uwaterloo.ca/apache/zookeeper/zookeeper-3.4.7/zookeeper-3.4.7.tar.gz | tar -xzf - -C /opt \
    && mv /opt/zookeeper-3.4.7 /opt/zookeeper \
    && cp /opt/zookeeper/conf/zoo_sample.cfg /opt/zookeeper/conf/zoo.cfg

ENV JAVA_HOME /usr/lib/jvm/java-7-openjdk-amd64

EXPOSE 2181 2888 3888

To connect your host to the ports the container is listening on, you run your container with the -p command. For example, to expose ports from the container and connect those same ports to your host machine you can specify multiple ports to bind to.

docker run -d -p 2181:2181 -p 2888:2888 -p 3888:3888 sookocheff/zookeeper-docker:4

Version 5 - Setting a Working Directory

We installed Zookeeper to /opt/zookeeper. Since we will only be running Zookeeper on this container it makes sense to also set our working directory to the install location. This is accomplished with the WORKDIR keyword.

FROM debian:jesse

RUN apt-get update && apt-get install -y openjdk-7-jre-headless wget
RUN wget -q -O - http://mirror.csclub.uwaterloo.ca/apache/zookeeper/zookeeper-3.4.7/zookeeper-3.4.7.tar.gz | tar -xzf - -C /opt \
    && mv /opt/zookeeper-3.4.7 /opt/zookeeper \
    && cp /opt/zookeeper/conf/zoo_sample.cfg /opt/zookeeper/conf/zoo.cfg

ENV JAVA_HOME /usr/lib/jvm/java-7-openjdk-amd64

EXPOSE 2181 2888 3888

WORKDIR /opt/zookeeper

Version 6 - Setting Configuration Files

The VOLUME keyword is used to mount data into a Docker container. To configure the Zookeeper instance we declare our configuration files locally and have Docker read those configuration files using the mounted volumes specified with the VOLUME command.

FROM debian:jesse

RUN apt-get update && apt-get install -y openjdk-7-jre-headless wget
RUN wget -q -O - http://mirror.csclub.uwaterloo.ca/apache/zookeeper/zookeeper-3.4.7/zookeeper-3.4.7.tar.gz | tar -xzf - -C /opt \
    && mv /opt/zookeeper-3.4.7 /opt/zookeeper \
    && cp /opt/zookeeper/conf/zoo_sample.cfg /opt/zookeeper/conf/zoo.cfg

ENV JAVA_HOME /usr/lib/jvm/java-7-openjdk-amd64

EXPOSE 2181 2888 3888

WORKDIR /opt/zookeeper

VOLUME ["/opt/zookeeper/conf"]

To map your local directory to the volume you created, you use the -v option of the docker run command. Place your zoo.cfg file in a local directory and run the container. In the following example, any files in the directory ./conf will be mapped to the directory /opt/zookeeper/conf on the container.

docker run -it -v conf:/opt/zookeeper/conf sookocheff/zookeeper-docker:7 /bin/bash

OS X users will need to qualify the local directory with the full path rather than a relative directory.

Version 7 - Sharing Data

Zookeeper also keeps local data in a file system directory. By default that directory is /tmp/zookeeper. We can also mount that volume through Docker.

FROM debian:jesse

RUN apt-get update && apt-get install -y openjdk-7-jre-headless wget
RUN wget -q -O - http://mirror.csclub.uwaterloo.ca/apache/zookeeper/zookeeper-3.4.7/zookeeper-3.4.7.tar.gz | tar -xzf - -C /opt \
    && mv /opt/zookeeper-3.4.7 /opt/zookeeper \
    && cp /opt/zookeeper/conf/zoo_sample.cfg /opt/zookeeper/conf/zoo.cfg

ENV JAVA_HOME /usr/lib/jvm/java-7-openjdk-amd64

EXPOSE 2181 2888 3888

WORKDIR /opt/zookeeper

VOLUME ["/opt/zookeeper/conf", "/tmp/zookeeper"]

If your Zookeeper configuration is different from the default, use the appropriate mount point for the data volume.

Volumes are a complex subject in Docker. For more details, refer to the official Docker documentation.

Version 8 - Running Zookeeper

At this point, your Dockerfile installs Zookeeper, exposes the appropriate ports to the host filesystem, and mounts volumes for configuration and data files. The last thing we need is to actually run Zookeeper. For this we use the ENTRYPOINT and CMD keywords.

FROM debian:jessie

RUN apt-get update && apt-get install -y openjdk-7-jre-headless wget
RUN wget -q -O - http://mirror.csclub.uwaterloo.ca/apache/zookeeper/zookeeper-3.4.7/zookeeper-3.4.7.tar.gz | tar -xzf - -C /opt \
    && mv /opt/zookeeper-3.4.7 /opt/zookeeper \
    && cp /opt/zookeeper/conf/zoo_sample.cfg /opt/zookeeper/conf/zoo.cfg

ENV JAVA_HOME /usr/lib/jvm/java-7-openjdk-amd64

EXPOSE 2181 2888 3888

WORKDIR /opt/zookeeper

VOLUME ["/opt/zookeeper/conf", "tmp/zookeeper"]

ENTRYPOINT ["/opt/zookeeper/bin/zkServer.sh"]
CMD ["start-foreground"]

The ENTRYPOINT and CMD keywords provide a default executable that is run whenever the container is started. In this example, we start the Zookeeper server in the foreground.

Wrapping Up

At this point, we have a working Dockerfile for building a Docker image to run a Zookeeper instance. To build the Docker image, run:

> docker build -t sookocheff/docker-zookeeper:3.4.7 .

And to run it:

> docker run -p 2181:2818 -p 2888:2888 -p 3888:3888 sookocheff/docker-zookeeper:3.4.7