Liferay and Docker: Dockerised Liferay Workspace

Technical Blogs February 17, 2018 By Iacopo Colonnelli


In recent times, containers are becoming more and more important in the software lifecycle management, whether we like it or not. The majority of  the leading software companies have already released a Docker version of their products, Microsoft did a huge work to adapt Windows kernel to Docker containers features, many Cloud services sell container-based solutions: containers are everywhere, and we can't just run away.

Also Liferay is playing its part in this. WeDeploy supports a Docker-based deployment and an official guide on how to deploy DXP on a Docker container has been released. We have come quite a journey since the first liferayctl proposal!

But wouldn't it be really nice to work with containers from the beginning of our development process to the last deploy in a production environment? I know that many blog articles, guides and speeches have been made on how to "Dockerise" Liferay, but a complete guide on how to transform our Liferay Workspace in a Dockerised Liferay Workspace, that allows developers to do everything they did before but with Liferay running on its own container, seems to be missing. And this is why I decided to write this blog article, hoping it can help. Enjoy!

Docker in Liferay Workspace

You can find an example of Docker integration in Liferay Workspace on this GitHub repository. It has been tested on a Windows machine running Docker for Windows, but it should work also with Docker Toolbox or with other OSs. Nevertheless, if you will experience some issues while trying it, please report them hereunder.

With this version of the Workspace, you will be able to start and stop a complex container environment, including Liferay 7 CE GA5 and other optional nodes (a separate DB, an Elasticsearch cluster, an Apache frontend, etc.) with just two Gradle tasks. Moreover, a third Gradle task allows you to build a Docker image of your current local environment, for versioning or test purposes or simply to share it with your colleagues or customers.

But the most important thing is that those features come without compromises: you will be able to debug your Liferay container form Eclipse, open a GoGo shell from your terminal, and even to use Gulp watch during theme development. But let's start from the core: the docker-compose file.

Docker Compose for multi-node environments

The core of the Dockerised workspace is the docker-compose.yml file. Even if you plan to run a simple Docker environment, with a single Liferay container, it's anyway better to have a docker-compose file to manage it. This is because you won't have to change anything in your environment start and stop Gradle tasks when you will be forced to use a more complex environment (and you will, be sure). A docker-compose file can manage multi-container contexts, with multiple nodes, networks and volumes: the only limitation will be your local machine processing power. For example you can:

  • Run a separate DB, like MySQL, when Hypersonic is not enough (quite always). In my example repository you will find a commented example on how to add a MySQL node
  • Run a separate Elasticsearch cluster. You will find a great example on how to do that on this Yasuyuki Takeo's blog post
  • Run a Liferay cluster with two or more nodes. You will find a great example on this Antonio Musarra's blog post on

In order to start and stop your environment, all you need to do is to launch the startDockerEnv and stopDockerEnv Gradle tasks, respectively. Those tasks are based on a bunch of additional Gradle properties that can be defined to partially customise their behaviour. Please refer to file in GitHub repository for more details.

Moreover, the docker-compose file defines a list of bind mounts that link your local deployment folders with the container filesystem, so that the stantard Gradle deployment tasks can be used as usual. For the Document Library, a Docker named volume is used instead, in order to persist DL contents when the container is restarted.

Dockerfile for environment snapshot

Another important element of this Dockerised Workspace is the Dockerfile. It allows developers to create a Liferay container that reflects the current local environment, in order to create a snapshot, by simply running the buildDockerImage Gradle task. Such an image can be used for multiple purposes in the different phases of the software lifecycle. For example:

  • As a test environment, maybe in conjunction with a CI tool (Jenkins, Travis CI, etc.). Since containers don't implicitly persist any generated data after they are removed, they are perfect for testing purposes
  • For environment versioning. When you reach a stable version of your code, you can tag it and release both a code archive and a working Docker image associated to that particular version. This can be realised manually, launching the Gradle task, or with an Automated build technique
  • For continuous deployment, again in conjunction with a CI tool. This requires that you can use Docker also in deployment target environments (or at least in a subset of them). The ideal case is when you can propagate the same container from your local machine to production environment, but also less ambitious solutions are possible
  • For WeDeploy deployment. You can add a wedeploy.json file and deploy your image on WeDeploy, following this guide

Liferay Docker containers

Two simple Liferay Docker containers have been realised for this article, in order to provide a starting point to further customisations. You can find them on Docker Hub or on this GitHub repository. In particular, there are:

  • The liferay-portal:7.0-ce-ga5 image, that inherits from openjdk:8 official container and adds a full Liferay 7 CE ga5 installation, exposing port 8080. The aforementioned Dockerfile in Dockerised Workspace inherits from this image
  • The liferay-portal:7.0-ce-ga5-dev image, that inherits from the previous container, adding some features that facilitate the development process. The aforementioned docker-compose file creates a container based on this image

In particular, with the latter image a developer can:

  • Run Liferay in developer mode. The file is attached to Liferay
  • Debug Liferay as a Remote Java Application. This image runs tomcat with JPDA enabled and a socket listening on the exposed port 8000. Docker Compose binds such port to the localhost 8000 port, so that a debugger can be attached directly on localhost:8000 (without having to know the container IP)
  • Open a local GoGo Shell. Setting the property to (instead of the default localhost:11311 value) and exposing port 11311, a remote connection to the GoGo shell is allowed. Docker Compose binds such port to the localhost 11311 port, so that the standard procedure described here can be used

The only thing that really changes between a standard Liferay Workspace and a Dockerised one is how to see Liferay logs. In a standard workspace, they can be viewed on the Eclipse console, while now you have to use the following command from a standard terminal (or Powershell):

docker logs -f [CONTAINER_NAME]

Docker Gulp Watch

Here comes the trickiest part. It would be very nice if also frontend developers could rely on the same Dockerised environment to build up Liferay Themes without compromises. Unfortunately, Gulp watch task doesn't work when Liferay runs inside a container. Furthermore, a non trivial amount of customisation to the standard Gulp pipeline is necessary to make it work.

This is why I decided to create a separate npm hook module to handle this integration. You can find such module published here on npm, together with a short description on how to use it with Yeoman generated Liferay Themes.

Basically, this module overrides some Gulp watch and deploy tasks, in order to copy files from a local .web_bundle_build folder to the container file system, using docker cp command. This allows Liferay runtime to succesfully update its packages. Moreover, since this process decouples files managed by Gulp from those handled by Liferay, it seems to solve also some file permission issues that occur on Windows systems and cause Gulp watch to stop working.


We reached the end of this containerised journey.
To sum up, this is an example of how Docker can be integrated into Liferay Workspace, so that developers can rely on all its beautiful features without be constrained to break their habits. I hope that such a guide can be useful to the community. If you have further ideas PLEASE share them hereunder: I hope that this journey is only another step in a much longer one.
Happy Liferay Coding!!!

Blueprint and OSGi Fragments for granular module extension

Technical Blogs December 2, 2017 By Iacopo Colonnelli


OSGi Fragments are a really powerful tool to extend the features of an existing bundle. Indeed, since fragments share classpath with their host bundle, they allow us to implement new functionalities avoiding unnecessary resource replications. Nevertheless, there are two main limitations that must be taken into account:

  • since fragments never reach the Active status, they are not able to neither register new services nor to override existing ones with an higher ranking
  • despite the classpath sharing feature, fragments cannot override host resources, because host context is searched before fragment context. Liferay JSP fragments are explicitly forced to behave differently by the JspFragmentTrackerCustomizer embedded into JspServlet, but this is not the default

So, what if we have to implement a new service that needs some existing resources located on a third-party bundle (i.e. a set of JSPs, a Soy template, language properties and so on)? Here comes the power of Blueprint framework. Indeed, if we declare our service via blueprint on our fragment, it will be injected into the host classpath when the fragment is attached and will be activated when the host bundle reaches it's Active state.

I will use the simple but practical example below in order to better clarify this concept.

Use case example

Few days ago, a friend of mine asked me how to extend a DDMFormFieldType component in order to make the indexable parameter configurable from UI. Therefore, such option is usually hidden by the visibilityExpression setting contained into DefaultDDMFormFieldTypeSettings implementation.

Obviously, in order to achieve our goal it's necessary to create a new DDMFormFieldTypeSettings class containing an indexType() method annotated with @DDMFormField(visibilityExpression = "TRUE") and tell our DDMFormFieldType component to refer to such settings file. The point here is: how can we do that best, by making the most of the OSGi paradigm modularity? Let's examine the possible (and not possible) solutions:

  • Create a new bundle that implements an higher ranked version of the DDMFormFieldType service. This is the most obvious solution, but with this implementation we are forced to duplicate also js resources and language properties. It should be our last choice, if we can't do anything better. But I'm sure we can ^_^
  • We don't want to replicate resources, so we should try with an OSGI Fragment and its wonderful classpath sharing feature. We could maybe create a fragment and override the settings file, like we did with Ext Plugin in previous versions of Liferay. This solution DOESN'T WORK, because the host context is searched before the fragment context, so original resources always take precedence
  • Therefore, we are forced to implement also our DDMFormFieldType service and inject the new settings file into it. It's no big deal after all. We can simply create our component and set an higher service ranking on it's properties, and Bob's your uncle. Again, this solution DOESN'T WORK, because since fragments do not reach the Active state, their services are not started
  • Well, our last solution is the good one and it's.....a combination of OSGi Fragments and Blueprint Framework. What an astonishing turn of events!!! With this strategy, we only need 3 files to achieve our goal:
    • Our custom DDMFormFieldTypeSettings class, with @DDMFormField(visibilityExpression = "TRUE") annotation on indexType() method
    • Our DDMFormFieldType class, without the component annotation, that refers to our settings file
    • An xml file, contained into the src/main/resources/OSGI-INF/blueprint folder, where we register our DDMFormFieldType implementation as a service with an higher ranking than the original. A simple tutorial on how to define services with blueprint framework syntax can be found here

An implementation of this simple example, realised for Liferay 7 CE ga5, can be found at In our case, we extended the ParagraphDDMFormFieldType service, but the same can be done for the other field types. Unfortunately, Liferay 7 CE ga5 doesn't come with an OOTB implementation of Blueprint, so we have to first install all required bundles in order to make it work. This process deserves a dedicated section: the next one.

Blueprint integration

Since Liferay has not an integrated implementation of the Blueprint Framework, we have to install a third-party library. Despite Eclipse Gemini Blueprint is the reference implementation for the OSGi Blueprint service, we chose to integrate Apache Aries Blueprint libraries for a couple of reasons:

  • As reported by LPS-56983, nobody is taking care of the Eclipse Gemini Blueprint project and it's not evolving at all. On the contrary, Apache Aries seems to have a more active community contributing to it
  • Apache Aries suite appears interesting also for other features, as the OSGi Subsystems described by David H Nebinger in this blog post

In order to install Apache Aries Blueprint suite in our Liferay environment, we only need to copy few bundles into the deploy folder:

Into the org-apache-aries-blueprint folder of the example repo there is the set of bundles that we used to enable Blueprint framework on our Liferay instance. Therefore, in order to run the example, you only have to:

  • Copy bundles from org-apache-aries-blueprint folder to your ${LIFERAY_HOME}/deploy folder
  • Deploy the fragment module on your Liferay instance

If the Fragment is correctly resolved, trying to add a new paragraph field to a form and clicking on Show More Options, you should see something like this


With this article, I'd like to bring your attention on how the combination of Blueprint Framework and OSGi Fragments can enhance even more the Liferay extensibility, giving to developers a powerful, versatile and easy-to-use tool to extend Liferay core, that is fully supported by the OSGi standard. Nevertheless, if you can find any drawbacks with this approach or if you have better ideas on how to handle such scenarios, please share them.
Happy Liferay coding!!!


Showing 2 results.