It seems like everyone is looking at what they should be containerising (yes, that’s a word!) in Docker to help improve their development, test, and production pipelines. With the recent release of Docker 1.12, it’s now easy to deploy Docker on a Raspberry Pi. As Liberty was the first application server to be shown running on a Raspberry Pi we’re always looking at how we can take advantage of the awesome little computers. So I set off on a mission to see how easy it is to get a Raspberry Pi-compatible Docker image of Liberty, and then to see how many instances of that image I can run on a single Raspberry Pi.

The first consideration when looking at what to put in a Docker container that will run on a small device is: how big is it when it is running? You don’t want to run out of memory with only one instance running, so you have to pick something that’s small and efficient. Fortunately WebSphere Liberty is small and lightweight so it was never a problem to fit it on the original Raspberry Pi back in 2012.

The complication when moving to a Raspberry Pi is that it¬†has an ARM CPU at its heart, meaning that machine code compiled for x86 or x86-64 CPUs (the standard CPUs you get in laptops/desktops today) won’t execute. Liberty is written in¬†Java so it doesn’t have to worry about what the CPU architecture is: as long as there is a Java runtime it’s able to run (as the Java runtime itself interprets the Java code and executes it on the hardware). We do have official Liberty images on Docker Hub, but they¬†are layered on top of the official IBM image, which is built on top of Ubuntu and doesn’t have a JVM that will run on ARM.

This means¬†you can’t take our Liberty Docker image from Docker Hub, which is designed for an x86 architecture, and run it on the Pi as-is. We need to edit the Dockerfiles that define how to build the x86 Liberty Docker image and modify them to build an image for the ARM Raspberry Pi.

Below are the steps I followed to modify the Dockerfiles that I used to build the official WAS Liberty image to create a Docker image that is capable of running on a Raspberry Pi. You can skip ahead to the final section if you want to read about how well Liberty scaled on the Raspberry Pi 3. The Dockerfiles I created are available on our GitHub page (if you don’t want to make the changes yourself).

Getting Docker on a Raspberry Pi

As this is a new project, we’re using the Raspberry Pi 3 as our test platform. This model is much more powerful than the original model B we ran on in 2012, as it now has 1 GB of RAM and a quad-core 1.2 GHz CPU. Built-in wireless also massively simplifies how easy it is for us to set up and run.

Installing an operating system on the Raspberry Pi

I installed NOOBS on the Raspberry Pi. NOOBS is an operating system installed from the Raspberry Pi foundation. With NOOBS installed you can quickly pull down your operating system of choice from a selection. Our choice was Raspbian as that’s very well supported in the community.

Installing Docker on the Raspberry Pi

Once you have Raspbian installed you need to get Docker on to the Pi. This is as easy as running the following command on the Pi:

curl -sSL get.docker.com | sh

That’s it! Once the command is finished, you’ll have Docker up and running on your Raspberry Pi!

Building a Liberty Docker image for the Pi

This is where you edit the Dockerfiles to build an ARM Liberty image for Docker on the Raspberry Pi.

If you don’t want to make these changes yourself, you can download my modified Dockerfiles from my ci.docker.raspberrypi GitHub repository and then skip to Running and scaling my deployment. If you want to make the changes yourself, keep reading.

There are two Dockerfiles in the ci.docker repository on GitHub which we will use in this article.

Building an image containing the Liberty kernel and JVM

You can edit the Dockerfile on the Raspberry Pi or on another machine but when you run the Dockerfile to build the image you must run it on an actual Raspberry Pi to make sure that you install the right JVM for the image. The Dockerfiles for the x86 Liberty Docker image are organised so that there is a kernel Dockerfile which takes the base IBM image and makes sure that all the basic capabilities are installed and available for downloading and running Liberty. You need to edit and run this Dockerfile first to build the basic image of the Liberty kernel with an appropriate JVM on an operating system:

  1. Take a copy of the kernel Dockerfile for the x86 Liberty Docker image.
  2. Edit the Dockerfile so that it will build the ARM Liberty Docker image instead. You need to add three things for the image:
    • A Linux kernel designed for ARM. I used the ioft/armhf-ubuntu image on Docker Hub. Edit the FROM target in the Dockerfile to build on top of that image (I used the 14.04 tag to get a 14.04 kernel because the 16.04 kernel didn’t seem to want to play):
      FROM ioft/armhf-ubuntu:14.04
          
    • A Java runtime that can run on ARM. OpenJDK has an ARM version that works. Add an entry in the `RUN` section of the Dockerfile:
      RUN apt-get update 
      && apt-get install -y --no-install-recommends unzip 
      && apt-get -y install openjdk-7-jre 
           
    • wget on the Raspberry Pi. We need to install wget so that we can to use the existing download code for Liberty later in the file. Again, just add an entry to the `RUN` section of the Dockerfile:
      RUN apt-get update 
      && apt-get install -y --no-install-recommends unzip 
      && apt-get -y install openjdk-7-jre 
      && apt-get -y install wget
            
  3. Replace the CMD final line in the file with the following command:
    CMD ["/opt/ibm/wlp/bin/server", "run", "defaultServer"]
      

    With those three changes (it should look like this), the rest of the script will run without any problems.

  4. Move the Dockerfile over to your Raspberry Pi (if you haven’t already), open up a terminal, and from the same directory as the Dockerfile, build your image with the following command:
    sudo docker build -t websphere-liberty:kernel .

This builds a local image called websphere-liberty with the kernel tag. This is the same name as you would pull down from Docker Hub and means we can use the other Dockerfiles that layer on top of it without needing to change their target (if you prefer, you can give this image a different name; just make sure you change the FROM target in the other Docker files to use whatever you call your image).

Adding Liberty features to the kernel image

Now we have the Liberty kernel image built and ready on the local Pi the hard work has been done, but the Liberty you have installed has no functionality. Due to the modular nature of the Liberty runtime, we can add whatever features we want on top of the basic kernel.

There is a webProfile7 Dockerfile that we could run as-is which just installs all of the Java EE 7 Web Profile features into Liberty. The test application I wanted to run, Ferret, is a very simple application that uses only the servlet capabilities to display information about its current runtime environment. So installing all the capabilities available in the Web Profile is overkill and wastes memory and disk space.

I wanted to use a little more than just the servlet, specifically: the servlet 3.1, jsp 2.3, localConnector 1.0, and websockets 1.1 Liberty features. Those are the features that I usually end up using for more basic Web applications. Modify the webProfile7 Dockerfile to add some additional capabilities to my image:

  1. Take a copy of the webProfile7 Dockerfile.
  2. Edit the Dockerfile so that it lists only the features we want in the main RUN block (this means that the Dockerfile will only download those components into the image, which helps save on disk space):
    RUN if [ ! -z $REPOSITORIES_PROPERTIES ]; then mkdir /opt/ibm/wlp/etc/ 
        && echo $REPOSITORIES_PROPERTIES > /opt/ibm/wlp/etc/repositories.properties; fi 
        && installUtility install --acceptLicense 
        servlet-3.1 jsp-2.3 websocket-1.1 localConnector-1.0 
        && if [ ! -z $REPOSITORIES_PROPERTIES ]; then rm /opt/ibm/wlp/etc/repositories.properties; fi 
        && rm -rf /output/workarea /output/logs
      
  3. Add the following two commands to copy a new server.xml configuration and a built WAR file of the Ferret application into the image (if you don’t put the server.xml and ferret.war files in the same directory as the Dockerfile, make sure you include the path as well as the filename):
    COPY server.xml /config/
    COPY ferret.war /opt/ibm/wlp/usr/servers/defaultServer/dropins
        

    It should look like this.

  4. Download the server.xml file that goes with this Dockerfile. It currently specifies that Liberty should load all the Web Profile 7 features but you can reduce this to improve memory usage and startup time because Liberty only loads the features you want to use. Edit the list to include only the features needed (the same list as you included in the Dockerfile above):
    <featureManager>
       <feature>servlet-3.1</feature>
       <feature>websocket-1.1</feature>
    </featureManager>
      

    It should look like this.

  5. With the Dockerfile and the server.xml file modified, copy both files across to the Raspberry Pi.
  6. In a terminal on the Raspberry Pi, change to the directory that contains the webProfile7 Dockerfile and run the following command to build an image that can be layered on top of the Liberty kernel Docker image that you built in the previous section of this article:
    sudo docker build tombanks/libertypi:pi3 .

This generates an image called tombanks/libertypi with the tag pi3 – this lets me check the image into my private Docker repository so I can pull it down to other Raspberry Pis with ease, and denotes that this is built on/for the Raspberry Pi 3. You can call this image whatever you like – just remember to use the name you give your image on the next steps.

Running and scaling my deployment

Once the image is built it’s time to start the container and see if it works! Run the following command to start the container:

sudo docker run -dP tombanks/libertypi:pi3

The -d part of the command runs the container in the background (so that I can use this terminal for other tasks) and the -P ensures the ports are opened. To know what port will be opened externally (ports are different for each container) run:

sudo docker ps

Your container will be listed there with the ports that are open. You should now (on the Pi) be able to open a web browser and point it to http://localhost:<port>/ferret to see the Ferret application up and running!

Now we have one container running, it’s time to see how many we can get running simultaneously. We’re running on a single Raspberry Pi so we don’t need to use Swarm for this. Just run the run command again to spin up an additional container on the same Rasberry Pi. Run the docker ps command to list all of the running containers so that you can see how many you have running and their port numbers.

I managed to run 13 containers with the Ferret application before the Pi really started to struggle. The main issue is how much RAM is in use so reducing the number of Liberty features used or disabling the UI and other unnecessary parts of the host Raspbian OS would let you run even more containers at once! Each time you spin up a container it’s best to also open the Ferret application in a browser in that container because, by default, Liberty doesn’t fully load a deployed application until it gets a request in.

If you do take on the challenge to see how many Liberty containers you can run on a single Raspberry Pi drop us a tweet at @WASdevnet. See if you can do better!

2 comments on"Liberty in Docker on a Raspberry Pi"

  1. Thanks to edward, the dockerfile is still not working.
    You need to change LIBERTY_VERSION from 16.0.0_2 or 16.0.0.2 to 17.0.0.2.
    If it is still not working, look in https://public.dhe.ibm.com/ibmdl/export/pub/software/websphere/wasdev/downloads/wlp/index.yml which LIBERTY_VERSION is the last one.

    Best Regards, Noah

  2. I’ve had difficulty running this and found I had to change the following:
    1. LIBERTY_VERSION from 16.0.0_2 to 16.0.0.2 (or 16.0.0.3)
    2. change “grep $LIBERTY_VERSION -A 6” in wget (line 31) to “grep $LIBERTY_VERSION”. For me this was resulting in the selection and download of two Liberty versions, which were clashing when it tried to unzip.

Join The Discussion

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