Running JavaFX in a Docker container

There’s lots of talk nowdays about the cloud, containerization, so I decided to see what it would take to run a JavaFX application within a docker container.

It took a bit of work to figure out, but it works great.

Due to some Oracle licensing requirements the version of Oracle Java 8 is not bundled like normal linux packages, but rather has to be downloaded and installed from a special repository called webupd8.  This small amount of additional complexity is also reflected in the Dockerfile we will cover below.

Why Docker?

Why bother bundling an application with Docker?  Well, there are many reasons.

  1. Reproducible installations — Since docker ships a machine with the application, if it runs on the source application container, it’s almost guaranteed to run on the target environment.
  2. Easy installations.
  3. Doesn’t touch your machine directly.
  4. It seems to be the emerging defacto cloud containerization standard.

Without further adieu, here’s my Dockerfile, let’s walk through it.

###
#
# Dex Dockerfile installation:
#
# build:
#
# docker build -t dex .
#
# run:
#
# docker run -v /tmp/.X11-unix:/tmp/.X11-unix -e DISPLAY=unix$DISPLAY dex
#
###

# pull base image
FROM debian:jessie

RUN \
    echo "===> add webupd8 repository..."  && \
    echo "deb http://ppa.launchpad.net/webupd8team/java/ubuntu trusty main" | tee /etc/apt/sources.list.d/webupd8team-java.list  && \
    echo "deb-src http://ppa.launchpad.net/webupd8team/java/ubuntu trusty main" | tee -a /etc/apt/sources.list.d/webupd8team-java.list  && \
    echo "deb http://ftp.de.debian.org/debian jessie main" >> /etc/apt/sources.list && \
    apt-key adv --keyserver keyserver.ubuntu.com --recv-keys EEA14886 && \
    apt-get update && \
    apt-get install -y gtk2-engines libxtst6 libxxf86vm1 freeglut3 libxslt1.1 && \
    apt-get update  && \
    \
    echo "===> install Java"  && \
    echo debconf shared/accepted-oracle-license-v1-1 select true | debconf-set-selections  && \
    echo debconf shared/accepted-oracle-license-v1-1 seen true | debconf-set-selections  && \
    DEBIAN_FRONTEND=noninteractive  apt-get install -y --force-yes oracle-java8-installer oracle-java8-set-default  && \
    \
        apt-get install -y git && \
        cd ~ && git clone https://github.com/PatMartin/Dex.git && \
    \
    echo "===> clean up..."  && \
    rm -rf /var/cache/oracle-jdk8-installer && \
    apt-get clean  && \
    rm -rf /var/lib/apt/lists/*

# cd to the Dex directory and execute the jar.
CMD cd ~/Dex && java -jar Dex.jar

Step 1:

In line 16 we see:

FROM debian:jessie

This simply tells docker to download, install and use a base debian installation using the jesse release as our base imate.

Step 2:

Next on lines 18 through 39 we see a pretty intimidating command:

RUN \
 echo "===> add webupd8 repository..." && \
 echo "deb http://ppa.launchpad.net/webupd8team/java/ubuntu trusty main" | tee /etc/apt/sources.list.d/webupd8team-java.list && \
 echo "deb-src http://ppa.launchpad.net/webupd8team/java/ubuntu trusty main" | tee -a /etc/apt/sources.list.d/webupd8team-java.list && \
 echo "deb http://ftp.de.debian.org/debian jessie main" >> /etc/apt/sources.list && \
 apt-key adv --keyserver keyserver.ubuntu.com --recv-keys EEA14886 && \
 apt-get update && \
 apt-get install -y gtk2-engines libxtst6 libxxf86vm1 freeglut3 libxslt1.1 && \
 apt-get update && \
 \
 echo "===> install Java" && \
 echo debconf shared/accepted-oracle-license-v1-1 select true | debconf-set-selections && \
 echo debconf shared/accepted-oracle-license-v1-1 seen true | debconf-set-selections && \
 DEBIAN_FRONTEND=noninteractive apt-get install -y --force-yes oracle-java8-installer oracle-java8-set-default && \
 \
 apt-get install -y git && \
 cd ~ && git clone https://github.com/PatMartin/Dex.git && \
 \
 echo "===> clean up..." && \
 rm -rf /var/cache/oracle-jdk8-installer && \
 apt-get clean && \
 rm -rf /var/lib/apt/lists/*

It would be nice to break it up some, but there’s a good reason that we don’t.  Multiple RUNs within a Dockerfile will cause the creation of multiple layers; one per RUN.  We’re trying to minimize this, and the objective causes some ugliness.  It’s necessary though.

Basically, this command is performing the following task:

  1. Lines 19-23 are setting up some repositories.
  2. Line 24 updates the packages.
  3. Line 25 installs a few necessary dependencies required for JavaFX libprism dependencies.
  4. Lines 29-30 automate the acceptance of conditions and lines 31 performs a headless installation of Java8.
  5. Line 33-34 installs git then uses it to download the Dex repository, code and all to the current directory.
  6. Lines 36-39 perform some cleanup.
  7. Line 42 specifies the command we will run by default when executing this application container.

Building from Dockerfile

Assuming docker is installed and running, drop the Dockerfile described above into an empty directory somewhere.

Issuing the command:

docker build -t dex .

Will create a dex image.  It’ll take a few minutes to run.  Remember, it’s creating a vm image, updating it to spec, installing oracle and dex.  That’s a lot!

Next, we can run it from an X11 capable linux client like this:

xhost +
docker run -v /tmp/.X11-unix:/tmp/.X11-unix -e DISPLAY=unix$DISPLAY dex

It should be possible to run like this from Windows or OSX, but I haven’t tried it personally.

Anyway, that’s it for now…Good luck!

  • Pat

About patmartin

I am a coder and Data Visualization/Machine Learning enthusiast.
This entry was posted in General. Bookmark the permalink.

8 Responses to Running JavaFX in a Docker container

  1. Pingback: JavaFX links of the week, April 11 | JavaFX News, Demos and Insight // FX Experience

  2. Alex says:

    Hi, thanks for your post 🙂 Unfortunately, it didn’t work for me (Ubuntu 15.10). Have I missed something obvious? Please see the stacktrace below.

    By the way: Splitting up that big RUN command into multiple small RUN commands comes with an advantage. The ‘multiple layers’ you mentioned will be reused in later builds (if possible) in order to speed up image creation. E.g., if you’d change your Git repo’s URL, docker could reuse the previously downloaded & installed software.

    $ docker run -v /tmp/.X11-unix:/tmp/.X11-unix -e DISPLAY=unix$DISPLAY dex
    No protocol specified
    No protocol specified
    Exception in thread “main” java.lang.reflect.InvocationTargetException
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at sun.launcher.LauncherHelper$FXHelper.main(LauncherHelper.java:767)
    Caused by: java.lang.UnsupportedOperationException: Unable to open DISPLAY
    at com.sun.glass.ui.gtk.GtkApplication.(GtkApplication.java:68)
    at com.sun.glass.ui.gtk.GtkPlatformFactory.createApplication(GtkPlatformFactory.java:41)
    at com.sun.glass.ui.Application.run(Application.java:146)
    at com.sun.javafx.tk.quantum.QuantumToolkit.startup(QuantumToolkit.java:257)
    at com.sun.javafx.application.PlatformImpl.startup(PlatformImpl.java:211)
    at com.sun.javafx.application.LauncherImpl.startToolkit(LauncherImpl.java:675)
    at com.sun.javafx.application.LauncherImpl.launchApplicationWithArgs(LauncherImpl.java:337)
    at com.sun.javafx.application.LauncherImpl.launchApplication(LauncherImpl.java:328)
    … 5 more

    • patmartin says:

      I forgot to comment on your other very valid point. I did fail to mention the trade-offs between a single image build and the fact that it may be advantageous to layer the images if they have reuse value as a base for other images.

      Also, when rebuilding this myself, for some reason the git clone failed on the first run and I had to re-run the entire thing. A layered build would have sped that second run up a bit.

  3. patmartin says:

    Hey Alex,

    Thank you for your reply. I was able to reproduce and fix the issue you are having by running the command:

    xhost +

    Prior to running the docker application.

    Hope this helps you!

    – Pat

  4. Hi! great post!
    I wonder will that work with “FROM ubuntu:14.04”, and what changes are needed, if any?

  5. Lee says:

    Can the docker image be packed to select a compatible version of Java 8?
    i.e. Time has moved on and the latest Java8 does not appear to be compatible with fx controls used by Dex.
    Thanks,
    Lee

    • patmartin says:

      Probably, or maybe I just keep it rolling with the latest and greatest. I just refreshed it and its working again. I had to push a new Dex.jar with refreshed libraries which are compatible with the current JavaFX changes. When I tried to run from Fedora, I ran into X11 permission issues. After spending a couple of hours with no progress, I just switched my host desktop to Ubuntu 16.04 which has a later version of Docker and my problems went away and it ran as expected.

Leave a comment