Pro Liferay Deployment

The Liferay deployment docs are great, but pros don't use them.

Introduction

The official Liferay deployment docs are available here: https://dev.liferay.com/discover/deployment

They make it easy for folks new to Liferay to get the system up and running and work through all of the necessary configuration.

But it is not the process followed by professionals. I wanted to share the process I use that it might provide an alternative set of instructions that you can use to build out your own production deployment process.

The Bundle

Like the Liferay docs, you may want to start from a bundle; always start from the latest bundle you can. It is, for the most part, a working system that may be ready to go. I say for the most part because many of the bundles are older versions of the application servers. This may or may not be a concern for your organization, so consider whether you need to update the application server.

You'll want to explode the bundle so all of the files are ready to go.

If you are using DXP, you'll want to download and apply the latest fixpack. Doing this before the first start will ensure that you won't need to deal with an upgrade later on.

The Database

You will, of course, need a database for Liferay to connect to and set up. I prefer to create the initial database using database specific tools. One key aspect to keep in mind is that the database must be set up for UTF-8 support as Liferay will be storing UTF-8 content.

Here's examples for what I use for MySQL/MariaDB:

create database lportal character set utf8;
grant all privileges on lportal.* to 'MyUser’@‘192.168.1.5' 
  identified by 'myS3cr3tP4sswd';
flush privileges;

Here's the example I use for Postgres:

create role MyUser with login password 'myS3cr3tP4sswd';
alter role MyUser createdb;
alter role MyUser superuser;
create database lportal with owner 'MyUser' encoding 'UTF8' 
  LC_COLLATE='en_US.UTF-8' LC_CTYPE='en_US.UTF-8' template template0;
grant all privileges on database lportal to MyUser;

There's other examples available for other databases, but hopefully you get the gist.

From an enterprise perspective, you'll have things to consider such as a backup strategy, possibly a replication strategy, a cluster strategy, ... These things will obviously depend upon enterprise needs and requirements and are beyond the scope of this blog post.

Along with the database, you'll need to connect the appserver to the database. I always want to go for the JNDI database configuration rather than sticking the values in the portal-ext.properties. The passwords are much more secure in the JNDI database configuration.

For tomcat, this means going into the conf/Catalina/localhost directory and editing the ROOT.xml file as such:

<Resource name="jdbc/LiferayPool" auth="Container" type="javax.sql.DataSource"
  factory="com.zaxxer.hikari.HikariJNDIFactory"
  minimumIdle="5" maximumPoolSize="10" connectionTimeout="300000"
  dataSource.user="MyUser" dataSource.password="myS3cr3tP4sswd"
  driverClassName="org.mariadb.jdbc.Driver"
  dataSource.implicitCachingEnabled="true"
  jdbcUrl="jdbc:mariadb://dbserver/lportal?characterEncoding=UTF-8&
    dontTrackOpenResources=true&holdResultsOpenOverStatementClose=true&
    useFastDateParsing=false&useUnicode=true" />

Elasticsearch

Elasticsearch is also necessary, so the next step is to stand up your ES solution. Could be one node or a cluster. Get your ES system set up and collect your IP address(es). Verify that firewall rules allow for connectivity from the appserver(s) to the ES node(s).

With the ES servers, create an ES configuration file, com.liferay.portal.search.elasticsearch.configuration.ElasticsearchConfiguration.config in the osgi/config directory and set the contents:

operationMode="REMOTE"
clientTransportIgnoreClusterName="false"
indexNamePrefix="liferay-"
httpCORSConfigurations=""
additionalConfigurations=""
httpCORSAllowOrigin="/https?://localhost(:[0-9]+)?/"
networkBindHost=""
transportTcpPort=""
bootstrapMlockAll="false"
networkPublishHost=""
clientTransportSniff="true"
additionalIndexConfigurations=""
retryOnConflict="5"
httpCORSEnabled="true"
clientTransportNodesSamplerInterval="5s"
additionalTypeMappings=""
logExceptionsOnly="true"
httpEnabled="true"
networkHost="[_eth0_,_local_]"
transportAddresses=["lres01:9300","lres02:9300"]
clusterName="liferay"
discoveryZenPingUnicastHostsPort="9300-9400"

Obviously you'll need to edit the contents to use local IP address(es) and/or name(s). This can and should all be set up before the Liferay first start.

Portal-ext.properties

Next is the portal-ext.properties file. Below is the one that I typically start with as it fits most of the use cases for the portal that I've used. All properties are documented here: https://docs.liferay.com/ce/portal/7.0/propertiesdoc/portal.properties.html

company.default.web.id=example.com
company.default.home.url=/web/example
default.logout.page.path=/web/example
default.landing.page.path=/web/example
admin.email.from.name=Example Admin
admin.email.from.address=admin@example.com
users.reminder.queries.enabled=false
session.timeout=5
session.timeout.warning=0
session.timeout.auto.extend=true
session.tracker.memory.enabled=false
permissions.inline.sql.check.enabled=true
layout.user.private.layouts.enabled=false
layout.user.private.layouts.auto.create=false
layout.user.public.layouts.enabled=false
layout.user.public.layouts.auto.create=false
layout.show.portlet.access.denied=false
redirect.url.security.mode=domain
browser.launcher.url=
index.search.limit=2000
index.filter.search.limit=2000
index.on.upgrade=false
setup.wizard.enabled=false
setup.wizard.add.sample.data=off
counter.increment=2000
counter.increment.com.liferay.portal.model.Layout=10
direct.servlet.context.reload=false
search.container.page.delta.values=20,30,50,75,100,200
com.liferay.portal.servlet.filters.gzip.GZipFilter=false
com.liferay.portal.servlet.filters.monitoring.MonitoringFilter=false
com.liferay.portal.servlet.filters.sso.ntlm.NtlmFilter=false
com.liferay.portal.servlet.filters.sso.opensso.OpenSSOFilter=false
com.liferay.portal.sharepoint.SharepointFilter=false
com.liferay.portal.servlet.filters.validhtml.ValidHtmlFilter=false
blogs.pingback.enabled=false
blogs.trackback.enabled=false
blogs.ping.google.enabled=false
dl.file.rank.check.interval=-1
dl.file.rank.enabled=false
message.boards.pingback.enabled=false
company.security.send.password=false
company.security.send.password.reset.link=false
company.security.strangers=false
company.security.strangers.with.mx=false
company.security.strangers.verify=false
#company.security.auth.type=emailAddress
company.security.auth.type=screenName
#company.security.auth.type=userId
field.enable.com.liferay.portal.kernel.model.Contact.male=false
field.enable.com.liferay.portal.kernel.model.Contact.birthday=false
terms.of.use.required=false
# ImageMagick
imagemagick.enabled=false
#imagemagick.global.search.path[apple]=/opt/local/bin:/opt/local/share/ghostscript/fonts:/opt/local/share/fonts/urw-fonts
imagemagick.global.search.path[unix]=/usr/bin:/usr/share/ghostscript/fonts:/usr/share/fonts/urw-fonts
#imagemagick.global.search.path[windows]=C:\\Program Files\\gs\\bin;C:\\Program Files\\ImageMagick
# OpenOffice
# soffice -headless -accept="socket,host=127.0.0.1,port=8100;urp;"
openoffice.server.enabled=true
# xuggler
xuggler.enabled=true
#hibernate.jdbc.batch_size=0
hibernate.jdbc.batch_size=200
cluster.link.enabled=true
ehcache.cluster.link.replication.enabled=true
cluster.link.channel.properties.control=tcpping.xml
cluster.link.channel.properties.transport.0=tcpping.xml
cluster.link.autodetect.address=dbserver
company.security.auth.requires.https=true
main.servlet.https.required=true
atom.servlet.https.required=true
axis.servlet.https.required=true
json.servlet.https.required=true
jsonws.servlet.https.required=true
spring.remoting.servlet.https.required=true
tunnel.servlet.https.required=true
webdav.servlet.https.required=true
rss.feeds.https.required=true
dl.store.impl=com.liferay.portal.store.file.system.AdvancedFileSystemStore

Okay, so first of all, don't just copy this into your portal-ext.properties file as-is. You'll need to edit it for names, sites, addresses, etc. It also enables clusterlink and sets up use of https as well as the advanced filesystem store.

I tend to use TCPPING for my ClusterLink configuration as unicast doesn't have some of the connectivity issues. I use a standard configuration (seen below), and use the tomcat setenv.sh file to specify the initial hosts.

<!--
  TCP based stack, with flow control and message bundling. This is usually used when IP
  multicasting cannot be used in a network, e.g. because it is disabled (routers discard multicast).
  Note that TCP.bind_addr and TCPPING.initial_hosts should be set, possibly via system properties, e.g.
  -Djgroups.bind_addr=192.168.5.2 and -Djgroups.tcpping.initial_hosts=192.168.5.2[7800]
  author: Bela Ban
-->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="urn:org:jgroups"
    xsi:schemaLocation="urn:org:jgroups http://www.jgroups.org/schema/jgroups.xsd">
  <TCP bind_port="7800"
    recv_buf_size="${tcp.recv_buf_size:5M}"
    send_buf_size="${tcp.send_buf_size:5M}"
    max_bundle_size="64K"
    max_bundle_timeout="30"
    use_send_queues="true"
    sock_conn_timeout="300"

    timer_type="new3"
    timer.min_threads="4"
    timer.max_threads="10"
    timer.keep_alive_time="3000"
    timer.queue_max_size="500"

    thread_pool.enabled="true"
    thread_pool.min_threads="2"
    thread_pool.max_threads="8"
    thread_pool.keep_alive_time="5000"
    thread_pool.queue_enabled="true"
    thread_pool.queue_max_size="10000"
    thread_pool.rejection_policy="discard"

    oob_thread_pool.enabled="true"
    oob_thread_pool.min_threads="1"
    oob_thread_pool.max_threads="8"
    oob_thread_pool.keep_alive_time="5000"
    oob_thread_pool.queue_enabled="false"
    oob_thread_pool.queue_max_size="100"
    oob_thread_pool.rejection_policy="discard"/>
  <TCPPING async_discovery="true"
    initial_hosts="${jgroups.tcpping.initial_hosts:localhost[7800],localhost[7801]}"
    port_range="2"/>
  <MERGE3  min_interval="10000"
    max_interval="30000"/>
  <FD_SOCK/>
  <FD timeout="3000" max_tries="3" />
  <VERIFY_SUSPECT timeout="1500"  />
  <BARRIER />
  <pbcast.NAKACK2 use_mcast_xmit="false"
    discard_delivered_msgs="true"/>
  <UNICAST3 />
  <pbcast.STABLE stability_delay="1000" desired_avg_gossip="50000"
    max_bytes="4M"/>
  <pbcast.GMS print_local_addr="true" join_timeout="2000"
    view_bundling="true"/>
  <MFC max_credits="2M"
    min_threshold="0.4"/>
  <FRAG2 frag_size="60K"  />
  <!--RSVP resend_interval="2000" timeout="10000"/-->
  <pbcast.STATE_TRANSFER/>
</config>

Additionally, since I want to use the Advanced Filesystem Store, I need a osgi/config/com.liferay.portal.store.file.system.configuration.AdvancedFileSystemStoreConfiguration.cfg file with the following contents:

##
## To apply the configuration, place this file in the Liferay installation's 
## osgi/modules folder. Make sure it is named 
## com.liferay.portal.store.file.system.configuration.AdvancedFileSystemStoreConfiguration.cfg.
##

rootDir=/liferay/document_library

JVM & App Server Config

So of course I use the Deployment Checklist to configure JVM, GC and memory configuration. I do prefer to use at least an 8G memory partition. Also I add the JGroups initial hosts.

Conclusion

Conclusion? But we haven't really started up the portal yet, how can this be the conclusion?

And that is really the point. All configuration is done before the portal is launched. Any other settings that could be changed in the System Settings control panel, well those I would also create the osgi/config file(s) to hold the settings.

The more that is done in configuration pre-launch, the less likelihood there is of getting unnecessary data loaded, user public/private layouts that might not be needed, proper filesystem store out of the gate, ...

It really is how the pros get their Liferay environments up and running...

Blogs

Thank you for good post! What do  you mean under "Deployment Checklist" — where is it? Could you please write production ready configuration (memory limits, jvm options, gc configuration) please.

The link should point to a page where you can request the deployment checklist; it is a PDF file with various suggestions for environment setup. DXP clients already have access to the document.

 

I can't really give you production ready configuration because it varies depending upon your particular usage scenario.  If you are doing heavy content management activities, search, staging, etc your environment would be completely different than if you are hosting custom portlets communicating with internal systems.

 

It is why Liferay does not publish configuration parameters directly; one configuration will not always work for other configurations.

Does it mean that not DXP client can't get deployment suggestions (example of deployment checklist)? In documentation exist section about performance tuning, but in common way and only for LR 6.2

I have a problem with performance of LR7 control panel and try to have solution to fix it (post question in slack but nobody answer yet).

I believe the link goes to a form you fill out before you get to the deployment checklist. Perf tuning for 6 and 7 are similar, many of the items you found will still apply.

 

Control panel performance? That would be news to me.  If you haven't customized the control panel, it should be working just fine.

 

I don't know if slack is the best place for questions; try posting it to the forums.

I use LR 7.1 (with 4gb xmx) and my problem is default control panel slow: slow rendering page tree, access configuration and etc, but front side with custom theme works perfect. And strange thing: it become faster after visit Control Panel->Configuration->Server Administation section.