Cloud Native Experience Kubernetes Ready

CNE Kubernetes Ready: Quickstart on k3d

Deploy Liferay DXP on a local k3d cluster with ephemeral PostgreSQL and Elasticsearch instances running inside the cluster. Use this environment to validate the Kubernetes Ready deployment model before configuring Kubernetes Ready on your own infrastructure.

Warning

This setup is for evaluation only. PostgreSQL and Elasticsearch use ephemeral storage, and the deployment uses a trial license. Do not use this configuration for production environments.

Validate the Kubernetes Ready deployment model locally before configuring Kubernetes Ready on your own infrastructure:

Prerequisites

Install these tools:

ToolPurpose
Docker or PodmanContainer runtime
k3dLocal Kubernetes cluster
kubectlKubernetes CLI
HelmInstall the Helm chart
Important

Allocate at least 8 GB of memory and 4 CPU cores to the container runtime.

Configure the container runtime environment to support vm.max_map_count=262144 for Elasticsearch. Verify the current value:

sysctl vm.max_map_count

If necessary, update it:

sudo sysctl -w vm.max_map_count=262144

Create the k3d Cluster

Create the cluster and namespace:

k3d cluster create liferay-quickstart \
   --servers 1 \
   --agents 0 \
   --port "8080:8080@loadbalancer"

kubectl create namespace liferay-quickstart

Verify the cluster:

kubectl cluster-info --context k3d-liferay-quickstart
Tip

k3d includes the local-path StorageClass by default, so the Liferay persistent volume claim works without additional configuration.

Deploy PostgreSQL and Elasticsearch

Create a quickstart-deps.yaml file to deploy PostgreSQL and Elasticsearch inside the cluster:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: postgres
  namespace: liferay-quickstart
spec:
  replicas: 1
  selector:
    matchLabels:
      app: postgres
  template:
    metadata:
      labels:
        app: postgres
    spec:
      containers:
        - name: postgres
          image: postgres:16
          env:
            - name: POSTGRES_USER
              value: liferay
            - name: POSTGRES_PASSWORD
              value: liferaypass
            - name: POSTGRES_DB
              value: lportal
            - name: PGDATA
              value: /var/lib/postgresql/data/pgdata
          ports:
            - containerPort: 5432
              name: tcp
          volumeMounts:
            - name: data
              mountPath: /var/lib/postgresql/data
      volumes:
        - name: data
          emptyDir: {}
---
apiVersion: v1
kind: Service
metadata:
  name: postgres
  namespace: liferay-quickstart
spec:
  selector:
    app: postgres
  ports:
    - name: tcp
      port: 5432
      targetPort: 5432
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: elasticsearch
  namespace: liferay-quickstart
spec:
  replicas: 1
  selector:
    matchLabels:
      app: elasticsearch
  template:
    metadata:
      labels:
        app: elasticsearch
    spec:
      containers:
        - name: elasticsearch
          image: docker.elastic.co/elasticsearch/elasticsearch:8.13.4
          env:
            - name: discovery.type
              value: single-node
            - name: xpack.security.enabled
              value: "false"
            - name: ES_JAVA_OPTS
              value: "-Xms1g -Xmx1g"
          ports:
            - containerPort: 9200
              name: http
          volumeMounts:
            - name: data
              mountPath: /usr/share/elasticsearch/data
      volumes:
        - name: data
          emptyDir: {}
---
apiVersion: v1
kind: Service
metadata:
  name: elasticsearch
  namespace: liferay-quickstart
spec:
  selector:
    app: elasticsearch
  ports:
    - name: http
      port: 9200
      targetPort: 9200

Apply the configuration:

kubectl apply -f quickstart-deps.yaml

Wait for both deployments to become available:

kubectl --namespace liferay-quickstart \
   wait --for=condition=Available deployment/postgres deployment/elasticsearch \
   --timeout=5m

Create Kubernetes Secrets

Create the database Secret:

kubectl --namespace liferay-quickstart create secret generic liferay-database \
   --from-literal=DATABASE_HOST=postgres \
   --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=liferaypass

Create the search Secret:

kubectl --namespace liferay-quickstart create secret generic liferay-search \
   --from-literal=ELASTICSEARCH_URL=http://elasticsearch:9200

Install Liferay DXP

Create a quickstart-values.yaml file that contains this Liferay DXP configuration:

image:
  repository: liferay/dxp
  tag: "[dxp-image-tag]"  # use any current tag, e.g. 2024.q4.10
  pullPolicy: IfNotPresent

replicaCount: 1

resources:
  requests:
    cpu: 1000m
    memory: 4Gi
  limits:
    cpu: 4000m
    memory: 8Gi

volumeClaimTemplates:
  - metadata:
      name: liferay-persistent-volume
    spec:
      accessModes:
        - ReadWriteOnce
      resources:
        requests:
          storage: 5Gi

customEnvFrom:
  x-liferay-database:
    - secretRef:
        name: liferay-database

  x-liferay-search:
    - secretRef:
        name: liferay-search

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

configmap:
  data:
    com.liferay.portal.search.elasticsearch8.configuration.ElasticsearchConfiguration.config: |
      authenticationEnabled=B"false"
      httpSSLEnabled=B"false"
      networkHostAddresses=["$[env:ELASTICSEARCH_URL]"]
      operationMode="REMOTE"
      productionModeEnabled=B"true"

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

Install the Helm chart:

helm upgrade --install liferay \
   oci://us-central1-docker.pkg.dev/liferay-artifact-registry/liferay-helm-chart/liferay-default \
   --namespace liferay-quickstart \
   --values quickstart-values.yaml

Verify the Deployment

Wait for the StatefulSet rollout to complete:

kubectl --namespace liferay-quickstart \
   rollout status statefulset/liferay-default --timeout=20m

View the logs:

kubectl --namespace liferay-quickstart \
   logs -f statefulset/liferay-default

Wait for this message:

Server startup in [n] milliseconds

Access Liferay DXP

Port-forward the Service:

kubectl --namespace liferay-quickstart \
   port-forward svc/liferay-default 8080:8080

Open http://localhost:8080 in a browser.

Retrieve the generated administrator password:

kubectl --namespace liferay-quickstart \
   get secret liferay-default \
   -o jsonpath='{.data.LIFERAY_DEFAULT_PERIOD_ADMIN_PERIOD_PASSWORD}' | base64 -d

echo

Use this administrator user:

test@liferay.com

Validate the Deployment

Confirm the deployment works correctly before configuring additional Kubernetes Ready properties:

  • The welcome page loads successfully.

  • You can sign in with the generated administrator password.

  • Elasticsearch reports an active connection.

  • Documents and Media uploads function properly.

  • The trial license appears in License Manager.

Remove the Environment

Run these commands to remove the quickstart deployment and delete the local k3d cluster:

helm --namespace liferay-quickstart uninstall liferay

kubectl delete -f quickstart-deps.yaml

kubectl delete namespace liferay-quickstart

k3d cluster delete liferay-quickstart

Next Steps

After validating the quickstart deployment, continue configuring Kubernetes Ready for production environments:

  • Configure Kubernetes Ready on your own Kubernetes environment.

  • Replace the in-cluster PostgreSQL and Elasticsearch deployments with managed services.

  • Configure ingress, TLS, object storage, and observability.

  • Review the Kubernetes Ready deployment and configuration articles for production guidance.