Posts about Web Development, Java, Magnolia CMS and beyond

5/03/2020

Trying out Docker with Magnolia CMS

5/03/2020 Posted by Edwin Guilbert , , 7 comments

Deploying old fashion way

When developing web apps sooner or later you need to handle the deployment of your app. A web server is needed to run your app so you need to install one which in some case needs deep knowledge about the one you go for.

In the case of java web apps, like Magnolia CMS, there are hundreds of options to choose from. In this post we will use Tomcat to deploy Magnolia.

Why Tomcat?

Tomcat is a good candidate since its very well adopted by the java community and its a good compromise between its feature set and its low complexity (it doesn't support EARs for example).

The idea of downloading, installing and configuring Tomcat over and over again every time you want to deploy and run your app can be cumbersome and tiring. Specially when you also have to install a database and/or want to take advantage of cloud platforms and services.

Deploying container way


This is where containers like Docker can be helpful since the only thing you need to do is run a Tomcat image and then copy your WAR file there and you are ready to go. Any OS, library, tool, JDK, etc has been already handled and set-up for you. And on top of that you can move your environment to any cloud platform and it will run just as smooth as in your on-premise/local one.

In the case of Magnolia, there are a couple of images out there already available. None of them is official and are somehow outdated. In this post, I will try to build yet another one, from scratch, so anyone can do the same with their own flavours of OS, server and deployment configuration.

Base image

The first step is to choose a base image with tomcat already installed and ready to deploy your web app. There are many images available in Docker Hub, including the official ones.

Just for fun (and also to feel a bit more in control of the environment) let's pick an OpenJDK image based on Debian slim version. 

Why the slim version? 

The idea of docker containers is to provide the minimal resources needed for a service in particular, so for Magnolia this would mean the minimum required to run Tomcat and deploy Magnolia on it. Debian has been experimenting with "providing a slimmer base (removing some extra files that are normally not necessary within containers, such as man pages and documentation)"

Why Debian?

Actually my first attempt was to use an alpine linux image with OpenJDK but I stumbled upon some issues with glib libraries that were needed by Magnolia. Apparently there is a workaround in case someone one wants to experiment with it :) 

Regarding Debian, is widely used in docker images and in general, so it's a safe bet.

Base Dockerfile

In docker, you need a dockerfile in order to build and run a container. The dockerfile is a way to describe all the things needed to create and run a container which provides a service.

I created a dockerfile based on openjdk 8 since its the minimum required by Magnolia, and then I just downloaded Tomcat, installed it and exposed port 8080. You can check the image at docker hub with the name: ebguilbert/tomcat-slim.

Note: I updated the dockerfile to use openjdk 11. But you can always checkout the previous commit.

Magnolia Container


Defining the image

Once we have our base image with an OS, JDK and Tomcat already set-up, the only thing needed is to copy a Magnolia war file and Tomcat will automatically deploy it.

This might seem like a simple step but in fact there are a couple of things you want to take care of when creating your own Magnolia Docker image:

The base image

Taken from the previous section, lets use ebguilbert/tomcat-slim:9 (9 is the tag stands for Tomcat 9.0.54 build)

FROM ebguilbert/tomcat-slim:9

Env variables


Environment variables are usually a good practice since they keep things organised in your dockerfile so anyone can check and modify relevant configuration at a glance. It's important to note that these variables will also be present in the running OS of the container so anyone can check them out by inspecting the image or by shell commands.

The first two variables are the version and the server to download the war from. These variables are only relevant for using a standard bundle, probably just for light development. In case you are building your own war, they are not needed anymore.

ENV MGNL_VERSION 6.2
ENV MGNL_SERVER https://files.magnolia-cms.com

Note: The version should be updated to the latest one, i.e 6.2.x, the URL (files.magnolia-cms.com) is only accesible for the latest version.

The following variables are very important, first the app main configuration folder, then the JCR repositories folder, logs folder and lastly the light-modules folder.

ENV MGNL_APP_DIR /opt/magnolia
ENV MGNL_REPOSITORIES_DIR ${MGNL_APP_DIR}/repositories
ENV MGNL_LOGS_DIR ${MGNL_APP_DIR}/logs
ENV MGNL_RESOURCES_DIR ${MGNL_APP_DIR}/light-modules

Arguments

Env variables are part of the image and can't be changed outside the dockerfile. But, if you define your variables as arguments you could change them at building time, which means the same dockerfile can be used for different configurations/builds.

Let's define the type of instance, the name of the war to be downloaded and the heap memory as arguments. We will see in the next section how we could use them to build different images.

ARG MGNL_AUTHOR=true
ARG MGNL_WAR=magnolia-dx-core-webapp
ARG MGNL_HEAP=2048M

JVM args

Since we are building a Java environment, it is very relevant to specify the heap memory that Tomcat should use to run our app. In this case we are using 2GB (minimum required by Magnolia). The other arguments are just the env/arg variables described previously.


ENV CATALINA_OPTS -Xms64M -Xmx${MGNL_HEAP} -Djava.awt.headless=true \
-Dmagnolia.bootstrap.authorInstance=${MGNL_AUTHOR} \
-Dmagnolia.repositories.home=${MGNL_REPOSITORIES_DIR} \
-Dmagnolia.author.key.location=${MGNL_APP_DIR}/magnolia-activation-keypair.properties \
-Dmagnolia.logs.dir=${MGNL_LOGS_DIR} \
-Dmagnolia.resources.dir=${MGNL_RESOURCES_DIR} \
-Dmagnolia.update.auto=true

Volume

Volumes are a way to share files between the host and the container. Without volumes, whatever you store in your container will be lost when you stop it. In the case of Magnolia we need to persist in the host machine the app folder since it will contain configurations like private/public keys, indexes, logs and the file-based contents.

VOLUME [ "${MGNL_APP_DIR}" ]

The war file to deploy

The final step is to obtain and copy the war file into the Tomcat's webapp folder so it's deployed when the container is started.

RUN wget -q ${MGNL_SERVER}/${MGNL_WAR}-${MGNL_VERSION}.war -O ${DEPLOYMENT_DIR}/ROOT.war

Like I said before I am using a standard bundle downloaded from Magnolia servers. But if you are providing your own war file you might need to change it to something like:

COPY my-application.war $DEPLOYMENT_DIR

It's also worth noticing that if you want to use the community standard bundle you will need to change the MGNL_SERVER and MGNL_WAR variables.


Note: If you use https://files.magnolia-cms.com URL and the certificate is invalid, wget will fail. In order to prevent that, try adding --no-check-certificate parameter to wget command


Building the image

In Magnolia you usually need at least one author and one public instance, so let's build an image for each:

docker build -t ebguilbert/magnolia-cms:6.2-author --build-arg MGNL_AUTHOR=true .

docker build -t ebguilbert/magnolia-cms:6.2-public --build-arg MGNL_AUTHOR=false .

Running the image

Managing volumes

Before running the image we need to create a volume in the host for each instance/container. In Magnolia you usually need at least one author and one public instance, so let's create one volume for each:

docker volume create mgnlauthor

docker volume create mgnlpublic

Managing network

A network is needed since the author instance needs to communicate to public instances for publication of content. If you don't provide any network when running the images, they will be in the default bridge network where both of them will be assigned dynamic IPs and they could be connected by IP. But this is not the desired way since we can't know the IP being assigned to each container (without inspecting the container with docker inspect).

For easy connection we will need to assign network aliases to the containers. In order to do that we will create our own network providing an ip range so they are isolated from other containers:

docker network create --subnet=192.168.42.0/24 mgnlnet

Running containers

We are finally ready to run our author and public containers, using our volumes and network:

docker run --rm -d -p 8080:8080/tcp --mount source=mgnlauthor,target=/opt/magnolia \
--network mgnlnet --name mgnlauthor ebguilbert/magnolia-cms:6.2-author


docker run --rm -d -p 8090:8080/tcp --mount source=mgnlpublic,target=/opt/magnolia \
--network mgnlnet --name mgnlpublic ebguilbert/magnolia-cms:6.2-public

Lets take a moment to review all the options provided:
  • Internally the containers are exposing port 8080, but the author is using port 8080 on the host and the public is using 8090. You can assign any port you like as the first parameter.
  • The volumes on the host will be linked to /opt/magnolia in the container.
  • Network mgnlnet is used by both containers where the author is called mgnlauthor and public is mgnlpublic.

Publishing content

Final step for a working Magnolia installation would be to configure the public instance as a receiver in the author instance. For that we will make use of the aliases provided in the network, let's configure the receiver in the publishing-core module of the author instance:


* publishing-core
* config
* receivers
* mgnlpublic
Notice we are using the container port and not the host one.

Final Dockerfile

FROM ebguilbert/tomcat-slim:9

LABEL maintainer="Edwin Guilbert"

# ENV variables for Magnolia
ENV MGNL_VERSION 6.2
ENV MGNL_SERVER https://files.magnolia-cms.com
ENV MGNL_APP_DIR /opt/magnolia
ENV MGNL_REPOSITORIES_DIR ${MGNL_APP_DIR}/repositories
ENV MGNL_LOGS_DIR ${MGNL_APP_DIR}/logs
ENV MGNL_RESOURCES_DIR ${MGNL_APP_DIR}/light-modules

# ARGS
ARG MGNL_AUTHOR=true
ARG MGNL_WAR=magnolia-dx-core-webapp
ARG MGNL_HEAP=2048M

# JVM PARAMS
ENV CATALINA_OPTS -Xms64M -Xmx${MGNL_HEAP} -Djava.awt.headless=true \
-Dmagnolia.bootstrap.authorInstance=${MGNL_AUTHOR} \
-Dmagnolia.repositories.home=${MGNL_REPOSITORIES_DIR} \
-Dmagnolia.author.key.location=${MGNL_APP_DIR}/magnolia-activation-keypair.properties \
-Dmagnolia.logs.dir=${MGNL_LOGS_DIR} \
-Dmagnolia.resources.dir=${MGNL_RESOURCES_DIR} \
-Dmagnolia.update.auto=true

# VOLUME for Magnolia
VOLUME [ "${MGNL_APP_DIR}" ]

RUN wget -q ${MGNL_SERVER}/${MGNL_WAR}-${MGNL_VERSION}.war -O ${DEPLOYMENT_DIR}/ROOT.war

Note: The version of Magnolia should be updated to the latest one, i.e 6.2.x, the URL (files.magnolia-cms.com) is only accesible for the latest version. Also if the URL's certificate is invalid, try adding --no-check-certificate to wget.

Database

Since this post ended up quite long already, let's talk about best practices for configuring a dockerized database in the next post.

7 comments:

  1. Hey there,
    have you tried running the community version with this?

    When I try it with the CE I get a Nullpointer Exception on Magnolias startup.
    (using the Magnolia CE 6.2.6 war file from sourceforge.net

    The Exception looks something like this:
    java.lang.NullPointerException
    info.magnolia.module.cache.filter.CacheFilter.doFilter(CacheFilter.java:144) info.magnolia.cms.filters.AbstractMgnlFilter.doFilter(AbstractMgnlFilter.java:85)
    ...

    Thanks for reading

    ReplyDelete
    Replies
    1. This comment has been removed by the author.

      Delete
    2. there seems to be a problem with some logs.

      $Error grabbing logs: unexpected EOF

      Delete
    3. Hi Quaspiel,

      Community should work just fine, just change the server+war URL to https://nexus.magnolia-cms.com/service/local/repositories/magnolia.public.releases/content/info/magnolia/bundle/magnolia-community-webapp/6.2.12/magnolia-community-webapp-6.2.12.war
      (or the sourceforge one)

      The exception trace you posted is too short, it would be better to attach the full log from the startup (from both instances)

      Delete
  2. there is a type:
    > docker build -t ebguilbert/magnolia-cms:6.2-author --build-arg MGNL_AUTHOR=false .
    should be
    > docker build -t ebguilbert/magnolia-cms:6.2-public --build-arg MGNL_AUTHOR=false .

    ReplyDelete
  3. Thnx! Updated.

    Hope you found the article useful :)

    ReplyDelete