CNE Kubernetes Ready: Configuring External Dependencies
Kubernetes Ready deployments rely on externally managed platform services for persistence, search, object storage, licensing, and secrets management. The liferay-default Helm chart does not provision these dependencies. Instead, it provides extension points that inject configurations into the Liferay container.
External services connect to Liferay through Kubernetes Secrets, ConfigMaps, mounted files, and Helm values.
- Secrets Management
- Database Configuration
- Search Configuration
- Object Storage Configuration
- Mounting OSGi Configuration Files
- License Configuration
These patterns move the database, search, and storage outside the pod so Liferay runs as a stateless workload in Kubernetes.
Concepts and Terminology
The Kubernetes Ready configuration patterns use a handful of Liferay-specific concepts. Skim this section before working through the snippets below.
OSGi PID: Liferay loads runtime configuration from OSGi .config files. The filename matches the Persistent Identifier (PID) of the configuration component — for example, com.liferay.portal.search.elasticsearch8.configuration.ElasticsearchConfiguration.config. Liferay watches /opt/liferay/osgi/configs/ for these files and applies any change without restarting the JVM.
Portal-property env-var encoding: Liferay accepts portal properties through environment variables. The encoding replaces . with _PERIOD_ and each capital letter in a camelCase fragment with _UPPERCASE<LETTER>. For example, the property jdbc.default.driverClassName becomes LIFERAY_JDBC_PERIOD_DEFAULT_PERIOD_DRIVER_UPPERCASEC_LASS_UPPERCASEN_AME. Use this rule whenever you set a portal property that contains uppercase letters.
${env.X} vs $[env:X]: Both syntaxes resolve environment variables at runtime, but in different files:
${env.VARIABLE_NAME}is resolved by Liferay’s portal-properties layer (used insidecustomEnvvalues).$[env:VARIABLE_NAME]is the OSGi.configplaceholder form (used insideconfigmap.dataentries).
Mixing the two is intentional; use the form that matches the surrounding file.
B"true" and typed-property values: OSGi .config syntax requires typed values: B"true" for booleans, ["a","b"] for arrays, and plain double-quoted strings for everything else. Omit the type prefix and the parser falls back to a string.
Cluster Link: Liferay’s intra-cluster communication channel on port 7800, used for cache invalidation and clustering messages between StatefulSet replicas. Routed through the liferay-default-headless Service. Never expose port 7800 externally.
Configuration Model
The liferay-default chart exposes several extension points for injecting configurations into the Liferay container.
This table defines the standard chart extension points:
| Extension Point | Purpose |
|---|---|
customEnv | Adds environment variables directly to the container |
customEnvFrom | Imports environment variables from Secrets or ConfigMaps |
configmap.data | Creates inline OSGi configuration files |
customVolumes | Adds additional Kubernetes volumes |
customVolumeMounts | Mounts files into the container filesystem |
Most production deployments use these specific configurations:
- Kubernetes Secrets for credentials
customEnvFromfor environment-variable injection- OSGi
.configfiles for service configuration - Mounted Secrets for licenses and certificates
Secrets Management
The recommended Kubernetes Ready pattern is to create separate Secrets for each external dependency.
This approach simplifies credential rotation, RBAC policies, vault synchronization, environment separation, and operational troubleshooting.
Run these commands to create standard sample configurations:
kubectl --namespace liferay create secret generic liferay-database \
--from-literal=DATABASE_HOST=db.internal.example.com \
--from-literal=DATABASE_PORT=5432 \
--from-literal=DATABASE_NAME=lportal \
--from-literal=LIFERAY_JDBC_PERIOD_DEFAULT_PERIOD_USERNAME=liferay \
--from-literal=LIFERAY_JDBC_PERIOD_DEFAULT_PERIOD_PASSWORD='********'
kubectl --namespace liferay create secret generic liferay-search \
--from-literal=ELASTICSEARCH_URL=https://search.internal.example.com:9200 \
--from-literal=ELASTICSEARCH_USERNAME=liferay \
--from-literal=ELASTICSEARCH_PASSWORD='********'
kubectl --namespace liferay create secret generic liferay-object-storage \
--from-literal=S3_BUCKET_NAME=acme-liferay-documents \
--from-literal=S3_BUCKET_REGION=us-east-1 \
--from-literal=S3_ACCESS_KEY_ID=AKIA... \
--from-literal=S3_SECRET_ACCESS_KEY='********'
Do not store plaintext credentials in Git repositories. In production environments, source Secrets from an external secret management system (such as External Secrets Operator, HashiCorp Vault, AWS Secrets Manager, Azure Key Vault, Google Secret Manager, or Sealed Secrets).
Importing Secrets with customEnvFrom
The chart’s customEnvFrom extension point injects all keys from a Secret or ConfigMap as environment variables inside the Liferay container.
This configuration maps external secrets into your deployment environment:
customEnvFrom:
x-liferay-database:
- secretRef:
name: liferay-database
x-liferay-search:
- secretRef:
name: liferay-search
x-liferay-object-storage:
- secretRef:
name: liferay-object-storage
After injection, the container references the values through ${env.VARIABLE_NAME} placeholders inside Liferay’s customEnv settings:
${env.DATABASE_HOST}
${env.ELASTICSEARCH_URL}
${env.S3_BUCKET_NAME}
Database Configuration
Configure Liferay database connectivity through environment variables mapped to portal properties.
PostgreSQL Configuration
This example handles a PostgreSQL database connection mapping:
customEnv:
x-liferay-database:
- name: LIFERAY_JDBC_PERIOD_DEFAULT_PERIOD_DRIVER_UPPERCASEC_LASS_UPPERCASEN_AME
value: org.postgresql.Driver
- name: LIFERAY_JDBC_PERIOD_DEFAULT_PERIOD_URL
value: jdbc:postgresql://${env.DATABASE_HOST}:${env.DATABASE_PORT}/${env.DATABASE_NAME}?useUnicode=true&characterEncoding=UTF-8&useFastDateParsing=false
The deployment loads the database username and password from the liferay-database Secret through customEnvFrom.
MySQL Configuration
This example handles a MySQL database connection mapping:
customEnv:
x-liferay-database:
- name: LIFERAY_JDBC_PERIOD_DEFAULT_PERIOD_DRIVER_UPPERCASEC_LASS_UPPERCASEN_AME
value: com.mysql.cj.jdbc.Driver
- name: LIFERAY_JDBC_PERIOD_DEFAULT_PERIOD_URL
value: jdbc:mysql://${env.DATABASE_HOST}:${env.DATABASE_PORT}/${env.DATABASE_NAME}
Confirm database compatibility against the Liferay Compatibility Matrix before deployment.
Search Configuration
Configure Liferay search connectors through OSGi .config files mounted into /opt/liferay/osgi/configs.
The correct connector choice depends on your DXP version and search engine version.
| DXP Version | Connector |
|---|---|
| DXP 2026.Q1 LTS+ | Elasticsearch 8 |
| DXP 7.3 to 2025.Q4 | Elasticsearch 7 |
| Any (alternative) | OpenSearch |
Elasticsearch 7 Configuration
This configuration registers an Elasticsearch 7 engine attachment:
configmap:
data:
com.liferay.portal.search.elasticsearch7.configuration.ElasticsearchConfiguration.config: |
authenticationEnabled=B"true"
httpSSLEnabled=B"true"
networkHostAddresses=["$[env:ELASTICSEARCH_URL]"]
operationMode="REMOTE"
password="$[env:ELASTICSEARCH_PASSWORD]"
productionModeEnabled=B"true"
username="$[env:ELASTICSEARCH_USERNAME]"
customVolumeMounts:
x-liferay-search:
- mountPath: /opt/liferay/osgi/configs/com.liferay.portal.search.elasticsearch7.configuration.ElasticsearchConfiguration.config
name: liferay-configmap
subPath: com.liferay.portal.search.elasticsearch7.configuration.ElasticsearchConfiguration.config
Elasticsearch 8 Configuration
This configuration registers an Elasticsearch 8 engine attachment:
configmap:
data:
com.liferay.portal.search.elasticsearch8.configuration.ElasticsearchConfiguration.config: |
authenticationEnabled=B"true"
httpSSLEnabled=B"true"
networkHostAddresses=["$[env:ELASTICSEARCH_URL]"]
password="$[env:ELASTICSEARCH_PASSWORD]"
productionModeEnabled=B"true"
username="$[env:ELASTICSEARCH_USERNAME]"
customVolumeMounts:
x-liferay-search:
- mountPath: /opt/liferay/osgi/configs/com.liferay.portal.search.elasticsearch8.configuration.ElasticsearchConfiguration.config
name: liferay-configmap
subPath: com.liferay.portal.search.elasticsearch8.configuration.ElasticsearchConfiguration.config
OpenSearch deployments use a different OSGi PID and compatibility matrix.
OpenSearch Configuration
If your platform uses OpenSearch instead of Elasticsearch, register an OpenSearch engine attachment with the corresponding OSGi PID:
configmap:
data:
com.liferay.portal.search.opensearch2.configuration.OpenSearchConfiguration.config: |
authenticationEnabled=B"true"
networkHostAddresses=["$[env:OPENSEARCH_URL]"]
password="$[env:OPENSEARCH_PASSWORD]"
productionModeEnabled=B"true"
username="$[env:OPENSEARCH_USERNAME]"
customVolumeMounts:
x-liferay-search:
- mountPath: /opt/liferay/osgi/configs/com.liferay.portal.search.opensearch2.configuration.OpenSearchConfiguration.config
name: liferay-configmap
subPath: com.liferay.portal.search.opensearch2.configuration.OpenSearchConfiguration.config
Check the Liferay Compatibility Matrix for the supported OpenSearch versions for your DXP release.
Object Storage Configuration
Store production deployment Documents and Media content in external object storage instead of the pod filesystem.
S3 Configuration
This example handles an external Amazon S3 storage integration:
customEnv:
x-liferay-storage:
- name: LIFERAY_DL_PERIOD_STORE_PERIOD_IMPL
value: com.liferay.portal.store.s3.S3Store
configmap:
data:
com.liferay.portal.store.s3.configuration.S3StoreConfiguration.config: |
bucketName="$[env:S3_BUCKET_NAME]"
s3Region="$[env:S3_BUCKET_REGION]"
accessKey="$[env:S3_ACCESS_KEY_ID]"
secretKey="$[env:S3_SECRET_ACCESS_KEY]"
customVolumeMounts:
x-liferay-storage:
- mountPath: /opt/liferay/osgi/configs/com.liferay.portal.store.s3.configuration.S3StoreConfiguration.config
name: liferay-configmap
subPath: com.liferay.portal.store.s3.configuration.S3StoreConfiguration.config
Equivalent store implementations exist for Google Cloud Storage, Azure Blob Storage, MinIO, and other S3-compatible APIs.
When using cloud workload identity (IRSA, GKE Workload Identity, or Azure AD Workload Identity), remove static access keys from Secrets and omit accessKey and secretKey from the OSGi S3 configuration.
Mounting OSGi Configuration Files
Apply this standard structure for mounting configuration values in Kubernetes Ready deployments:
-
Define OSGi configuration files through
configmap.data. -
Mount them into
/opt/liferay/osgi/configs.
This resource definition shows a completed mount example:
configmap:
data:
com.liferay.portal.search.elasticsearch7.configuration.ElasticsearchConfiguration.config: |
productionModeEnabled=B"true"
customVolumeMounts:
x-search:
- mountPath: /opt/liferay/osgi/configs/com.liferay.portal.search.elasticsearch7.configuration.ElasticsearchConfiguration.config
name: liferay-configmap
subPath: com.liferay.portal.search.elasticsearch7.configuration.ElasticsearchConfiguration.config
Liferay loads mounted .config files automatically during startup.
License Configuration
Store the Liferay license in a Kubernetes Secret and mount it into the auto-deploy directory.
Create the Secret:
kubectl --namespace liferay create secret generic liferay-license \
--from-file=license.xml=./license.xml
Mount the Secret:
customEnv:
x-license:
- name: LIFERAY_DISABLE_TRIAL_LICENSE
value: "true"
customVolumes:
x-license:
- name: liferay-license
secret:
secretName: liferay-license
customVolumeMounts:
x-license:
- mountPath: /etc/liferay/mount/files/deploy/license.xml
name: liferay-license
subPath: license.xml
Liferay deploys the license automatically during startup.
Recommended Production Pattern
Production Kubernetes Ready deployments use this architecture layout:
values.yaml
Secrets
├── liferay-database
├── liferay-search
├── liferay-object-storage
└── liferay-license
configmap.data
├── ElasticsearchConfiguration.config
└── S3StoreConfiguration.config
customEnvFrom
└── injects Secrets into the container
customVolumeMounts
└── mounts OSGi configs and license.xml