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.
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:
-
Verify the prerequisites.
-
Create the required Kubernetes Secrets.
-
Install and verify Liferay DXP locally.
Prerequisites
Install these tools:
| Tool | Purpose |
|---|---|
| Docker or Podman | Container runtime |
| k3d | Local Kubernetes cluster |
| kubectl | Kubernetes CLI |
| Helm | Install the Helm chart |
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
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.