Liferay Workspace Distribution Bundles

The Liferay Workspace comes with a great build option that I'm not sure developers know about and/or use - distribution bundles.

When you have your Gradle-based workspace, the simple command gradlew distBundleZip or gradlew distBundleTar can build you a prepackaged Liferay bundle using Liferay CE or DXP, Tomcat, your custom modules as well as your configuration for the targeted environment. The Maven-based workspace sports the same capabilities, you just use mvn instead of gradlew.

Why Do I Want A Custom Distribution Bundle?

Following the classic model, the developer compiles the customizations into jars and wars, these are deployed and promoted through the environments, eventually to be deployed to production. In production, you're often concerned about keeping some nodes up during deployment so users don't face any downtime, so you juggle the nodes in the cluster to get them updated and back in the cluster as soon as possible. These deployments, though, tend to be manual efforts, sometimes face problems because of environmental issues, etc.

The rise of containerization though showed us the truth - servers are not assets as much as they are commodities.

Seriously, we invest so much time into purchasing servers, installing software, performing updates and maintenance, monitoring availability and downtime, etc. We are basically cultivating assets that need protecting and nourishing.

Containerization showed us a different vision. A server isn't an asset, it is just a commodity we need to host our application. We actually minimize work and cost when we can commoditize our servers rather than treating them as assets.

That Liferay/Tomcat bundle you download to your workstation, expand, start and play with? That treats your local workspace as a commodity. Liferay doesn't give you a list of things you have to install, setup, configure, etc. It's basically just "have a JVM" and you can spin up Liferay...

So why do you need a custom distribution bundle? So you can commoditize your own servers, to use them to host your application. Your application, though, is Liferay and Tomcat and your customizations and your configuration. When you build a custom distribution bundle, you are packaging all of your stuff into a complete application, an application that you can spin up on any commodity server whether it is your local workstation, the dev/test/uat/prod tiers, or even on a cloud-based virtual server.

Configuring The Workspace For Distribution Bundles

Now that we're onboard with building distribution bundles, we know that our bundles are not always going to be the same. Sure they will have the same Liferay the same Tomcat, heck they should even have the same custom modules (jars and wars) across all of the environments.

The configuration, though, the configuration changes. You don't use the same portal-ext.properties in your local environment that you do in test, uat and especially prod. You don't use the same logging configuration. You don't use the same licensing. Heck, you might even be using different ports for tomcat.

When you are building your distribution bundle, you have to have a way to include the right configuration for the right environment.

In the Liferay Workspace, we accomplish this using "workspace environments". The Liferay Workspace allows you to define an environment by name, wrap all of the environment-specific configuration behind that environment, then at build time you can choose the environment to include into the distribution bundle.

So how do you define an environment? Add a folder in the workspace's configs folder.

If you look in the configs folder, you'll find that the Blade tool has already created a number of them for you (your Blade version may create a different set than seen here). Using Blade 3.7.4, my configs folder has the following pre-defined environments:

I get dev, docker, local, prod and uat environments. Common is not really an environment, it is meant to hold shared config that will be used in all of the environments.

The local environment is for your local workstation. The rest are for, well, the type of environment that you'd expect.

Each environment has the configuration files that define what the environment will be. The local environ just has a portal-ext.properties file because it doesn't need any other special configuration. The prod and uat environments, well they will be configured using an external Elasticsearch cluster, so they have an osgi/configs folder w/ their own Elasticsearch config file.

So what do you put inside these environment folders? Anything that you want to have copied into the environment such as your portal-ext.properties file, OSGi configuration files in osgi/configs, your XML license file in osgi/modules, JNDI database connection details in tomcat-9.0.x/conf/Catalina/localhost/ROOT.xml (the x will change depending upon the version of tomcat your bundle has), logging configuration details, JGroups configuration files, maybe some additional 3rd party jars in tomcat's lib/ext folder or webapps/ROOT/WEB-INF/lib, yada yada yada.

Basically anything that needs to overlay into an environment to make it functional. The only requirement is that you must use the path in the environment folder that matches the path in the bundle. So to put your Liferay developer license XML file for DXP into the local environ, it would be in the configs/local/osgi/modules directory.

NOTE: For the Tomcat overlays, if your bundle is using tomcat-9.0.17 but you have configs/local/tomcat-9.0.6/conf/Catalina/localhost/ROOT.xml file, well the workspace is not going to guess you wanted this file in 9.0.17, you'll end up with both a tomcat-9.0.6 and a tomcat-9.0.17 folders in your bundle but only one of them will really be tomcat.

You can include configs for any of the settings you would be using in the System Settings control panel. You can find out more about the .config files here: https://portal.liferay.dev/docs/7-1/user/-/knowledge_base/u/understanding-system-configuration-files.

When I use this kind of setup, I actually put portal-ext.properties and many other configuration details into the common folder so that generally I end up with consistency across all environs. In the portal-ext.properties file, I'll include a line:

include-and-override=portal-env.properties

This way I can create portal-env.properties in each of the environment folders to give them the local differences (i.e. database configuration if using the jdbc properties or including the portal-developer.properties in my local and dev environments, etc).

Setting the Distribution Bundle Template

In order to build a custom distribution bundle, you need a Liferay bundle to use as a template. This really isn't that difficult to set and, in fact, there is already a default assigned when you create a workspace.

In the root of your workspace is a gradle.properties file. Inside the file, you'll find a line such as the following:

liferay.workspace.bundle.url=https://releases-cdn.liferay.com/portal/7.1.3-ga4/liferay-ce-portal-tomcat-7.1.3-ga4-20190508171117552.tar.gz

The liferay.workspace.bundle.url property points at a Liferay bundle, this bundle is used when starting/stopping the server, etc.

It also happens to be the bundle used when you create your distBundleTar or distBundleZip.

This URL typically will by default point to a publicly available Liferay bundle that can be downloaded, but this maybe is not going to be a bundle you want to use. You might be a DXP customer and have a local 7.1 DXP Service Pack 2 bundle that you want to use instead. Or perhaps you read my previous blog (Creating A Custom Liferay Tarball) and are building an up-to-date bundle and you want to use that one instead...

If you have a server you can host that from, then just change the URL to a path appropriate for yourself and/or your organization. If you have the bundle locally, you can set the URL to be a file:// URL to point at the local file instead.

Creating the Distribution Bundles

At this point we have defined all of our various environments by creating and populating the folders in the workspace's configs directory.

We also would have updated the liferay.workspace.bundle.url to point to the bundle we want to base our distribution bundle on.

The last step is to, well, actually create the bundle.

The Gradle command is pretty simple:

gradlew distBundleTar

This will build a distribution bundle using the "local" environment, the default used when you don't specify an environment. To build a distribution bundle for a different environment, the command would be:

gradlew distBundleTar -Pliferay.workspace.environment=prod

Change the value to be the environment you want to build for and you're golden.

The first time you run the command, likely the bundle will need to be downloaded. The bundle will be cached so future runs will not need to download it again. In the build directory, the source bundle will be exploded.

As it is a Gradle build, all of your submodules (modules, themes and wars) will be built (if necessary), and the artifacts will be merged into the exploded bundle in the build directory.

The configurations from the configs/common folder and the environment-specific configs will also get merged into the exploded bundle in the build directory.

Finally your new custom distribution bundle, either a tar or a zip, will be created in the build folder. It will be named for your workspace and have either the .tar.gz extension (for distBundleTar) or .zip extension (for distBundleZip).

Conclusion

Now you have your application, the thing you will be deploying to your commoditized servers. At this point the only thing the server needs to have on it is the JDK; your custom distribution bundle has Tomcat, Liferay, and all of your configuration and customizations.

Your application is packaged and ready to run on someones desktop, a server or VM your company has available or even an EC2 instance you have in AWS. Other than getting a server commodity to run the application on, your operations folks aren't going to be doing much for the initial deploy. When you have new or updated modules to deploy, you're not pushing out jars and wars to deploy, you just give operations the new distribution bundle; they pull a node out of the cluster, delete the old bundle, explode your new bundle, then start the node to get it back in the cluster. In the case of VMs or EC2 instances, you could have new replacement nodes ready with the updated distribution bundle, have the load balancer redirect traffic to the new replacements and just discard the old nodes when they are no longer serving traffic.

Best of all, you get this custom distribution bundle stuff right out of the box in the Liferay Workspace. You don't have to roll your own mechanism to deal with environmental config differences, you don't have to manage, edit and deploy property changes as a separate part of the application setup and maintenance process.