Cloud Native Experience Kubernetes Ready

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.

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 inside customEnv values).
  • $[env:VARIABLE_NAME] is the OSGi .config placeholder form (used inside configmap.data entries).

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 PointPurpose
customEnvAdds environment variables directly to the container
customEnvFromImports environment variables from Secrets or ConfigMaps
configmap.dataCreates inline OSGi configuration files
customVolumesAdds additional Kubernetes volumes
customVolumeMountsMounts files into the container filesystem

Most production deployments use these specific configurations:

  • Kubernetes Secrets for credentials
  • customEnvFrom for environment-variable injection
  • OSGi .config files 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='********'
Tip

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}
Important

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 VersionConnector
DXP 2026.Q1 LTS+Elasticsearch 8
DXP 7.3 to 2025.Q4Elasticsearch 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
Note

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:

  1. Define OSGi configuration files through configmap.data.

  2. 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.

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