A few weeks ago I finally gave Docker a shot. It wasn’t a really smooth ride so I decided to write an article about it. I will run a Zookeeper as an example to get experience with Docker. After this description I’ll show you the obstacles I encountered and how to solve them. I will also provide some links to help you do some real life things with Docker.

For a long time I thought Docker was a big hype, but it seems things got more serious. A long while ago I gave Docker for Mac a shot, but it wasn’t really mature yet. Running Docker on a Virtual Machine was a no-go for me, it kind of defeats the purpose of Docker and I was really happy with Vagrant.

Docker for Mac

Docker for Mac is finally usable. It still has a few rough edges on the networking part, but good enough to use during development or create images that will run on a Linux production environment.

Which image should I use?

There are a lot of different images. I wanted a light weight image with just a jdk. The official java image for Docker is deprecated and the openjdk image is suggested as its successor. Then there’s a lot of choice. I picked openjdk:alpine because its description sounded pretty good : “Alpine Linux is much smaller than most distribution base images (~5MB), and thus leads to much slimmer images in general.”

Create a simple image that prints the java version

My first step was something even simpler than the obligatory ‘Hello World’, just print the Java version. This implicitly also shows that we’re dealing with OpenJDK instead of Oracle Java .

Create a file named Dockerfile with the following contents :

FROM openjdk:alpine CMD java

This will define an image based on the Alpine images and runs the command CMD . You can build the image with :

docker build -t jvwilge/hello-world

The image will be created and should be visible when you run docker images . The minus t option tags the image in the 'name:tag' format so it’s findable in the images list under jvwilge/hello-world . It’s a bit larger than the 5MB Alpine was advertised with, but still a pretty decent size.

To run the image execute

docker run -it --rm --name hello-world-1 jvwilge/hello-world

This will start the image and print the java version :

openjdk version "1.8.0_111-internal" OpenJDK Runtime Environment (build 1.8.0_111-internal-alpine-r0-b14) OpenJDK 64-Bit Server VM (build 25.111-b14, mixed mode)

The option rm removes the container after completion. Note the difference between container and image. A container is a running instance of an image. Containers are visible with docker ps . But since our fresh container quits immediately after completion and is very fast you won’t see anything there (but I’ll show you later).

Taking it to the next level: Building a Zookeeper image

Go to (or create) a new directory and create a file named Dockerfile :

FROM openjdk:alpine ARG ZK_VERSION=3.4.10 ENV ZK_HOME /home/zookeeper/zookeeper-$ZK_VERSION # zkServer.sh uses bash, so we have to install it. RUN apk add --update bash && rm -rf /var/cache/apk/* RUN adduser -S zookeeper USER zookeeper WORKDIR /home/zookeeper RUN wget http://apache.40b.nl/zookeeper/zookeeper-$ZK_VERSION/zookeeper-$ZK_VERSION.tar.gz RUN tar xfz zookeeper-$ZK_VERSION.tar.gz RUN rm zookeeper*.tar.gz ADD zoo.cfg $ZK_HOME/conf/zoo.cfg EXPOSE 2181 CMD $ZK_HOME/bin/zkServer.sh start-foreground

The image can be build with :

docker build -t jvwilge/zookeeper .

The ARG instruction defines a variable that can be changed at build time. A default value is optional. In the following example we can provide another version of Zookeeper :

docker build --build-arg ZK_VERSION=3.4.8 -t jvwilge/zookeeper .

The ENV instruction is almost the same, but cannot be overridden. Both ARG and ENV are variables that can be used in the RUN instruction.

Next step is to install bash (needed for zkServer.sh ) and add a user zookeeper. Switching to this user is done with the USER instruction.

With the WORKDIR command we switch to a working directory, download Zookeeper (note the $ZK_VERSION variable), extract it and remove the downloaded .tar.gz file.

With ADD you can add a file that is available on the host machine. In this case it is a zoo.cfg placed in the same directory as Dockerfie on the host machine.

EXPOSE exposes the Zookeeper port to the host machine (this can be mapped to another port number at runtime).

CMD starts the server. More on CMD vs ENTRYPOINT later.

Running the Zookeeper image

Run the image with :

docker run -d -it --rm -p 12181:2181 --name zookeeper1 jvwilge/zookeeper

With -p you can map the exposed port to another port. In this case zookeeper will be available on the host machine on port 12181.

Telnet to port 12181 and the ruok (are you ok?) command can verify things are working :

telnet localhost 12181 Trying ::1... Connected to localhost. Escape character is '^]'. ruok imok Connection closed by foreign host.

The image runs in the background (it’s technically an container now) and is visible with docker ps

CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 699d2c66c289 jvwilge/zookeeper "/bin/sh -c '$ZK_H..." 2 minutes ago Up 2 minutes 0.0.0.0:12181->2181/tcp zookeeper1

Stop the container with docker stop 699d2c66c289 . Where 699d2c66c289 is the CONTAINER ID.

CMD vs ENTRYPOINT (or service image vs. executable image)

The Docker documentation states “ENTRYPOINT should be defined when using the container as an executable.”. This is a bit vague when you don’t know what an executable image is and what the alternative is.

The alternative is a service image, a long running container (like a web server of a Kafka broker).

An executable image is short lived.

Source 1

Source 2

Bash/file not found in $PATH

Some applications depend on bash, when this is the case you can insert the following line to your Dockerfile :

RUN apk add --update bash && rm -rf /var/cache/apk/*

Source

‘I see a lot of images with tag <none>’

When you’re building an image as a less experienced user you will run into errors. Those error will leave scars in the form of a <none> tag. When you display the images with docker images you’ll probably see some. These images will clog up your hard disk eventually.

To remove these images you first have to remove the containers or otherwise you’ll get the following error

$ docker rmi a7b133710d3f Error response from daemon: conflict: unable to delete a7b133710d3f (must be forced) - image is being used by stopped container aa67b2abc73e

Of course you can add --force but this will still leave the containers on your disk. To remove the containers properly run :

docker rm $(docker ps -a -q)

Note that when then are no results at docker ps -a -q you will see the message "docker rm" requires at least 1 argument(s).

When this command is successful you can remove the images (without having to force it).

docker rmi $(docker images | grep '^<none>' | awk '{print $3}')

Source (note that you have to use single quotes)

‘I want to have a prompt/run shell commands on the container’

Logging in into your docker container is not intended, but sometimes you want to check things without having to rebuild your images.

To get a bash prompt on your container run

docker exec -t -i container_name /bin/bash

Note that bash needs to be installed to ‘bash into’ the container.

Source

Networking on the mac

There are some issues with networking with Docker on the Mac which are described in ‘Known Limitations, Use Cases, and Workarounds’ .

Note that the sudo ifconfig lo0 alias 10.200.10.1/24 command should be executed after every restart of your Mac!

Other useful links

I don’t want to withhold the links that didn’t make it into this article (but are very useful) :

How to get a Docker container’s IP address from the host?

Best practices for writing Dockerfiles

The JVM and Docker, a good idea? by Christopher Batey @ Devoxx UK 2016

Conclusion

I hope this article will save you some time when starting with Docker. Please feel free to comment on the article with the issues you ran into, this way we can help others to get started with Docker more quickly