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
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 dontesta.it
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 README.md 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 portal-developer.properties 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 module.framework.properties.osgi.console property to 0.0.0.0:11311 (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!!!