From 82f836357aaf6a4a216633f70c8427d57f952492 Mon Sep 17 00:00:00 2001
From: Kirby Chin <37311900+kabicin@users.noreply.github.com>
Date: Mon, 17 Aug 2026 12:58:07 -0400
Subject: [PATCH 1/5] Disable httpPort in server.xml
---
Dockerfile | 11 +++++++----
src/main/liberty/config/server.xml | 9 ++++++---
2 files changed, 13 insertions(+), 7 deletions(-)
diff --git a/Dockerfile b/Dockerfile
index 8f7ad429..f032decb 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,6 +1,7 @@
FROM icr.io/appcafe/open-liberty:kernel-slim-java11-openj9-ubi-minimal
ARG VERSION=1.0
ARG REVISION=SNAPSHOT
+ARG SKIP_LINPERF=false
LABEL \
org.opencontainers.image.authors="Alasdair Nottingham" \
@@ -15,10 +16,12 @@ LABEL \
summary="Sample app running on Open Liberty that uses Eclipse MicroProfile" \
description="This image contains a sample application that displays the Java system properties and demonstrates MicroProfile Config, Health and Metrics."
-# Install required packages to run linperf.sh
-USER 0
-RUN command -v yum && pkgcmd=yum || pkgcmd=microdnf && ($pkgcmd update -y && $pkgcmd install -y procps-ng net-tools ncurses hostname)
-USER 1001
+# Install required packages for running the Liberty MustGather (linperf.sh) script.
+RUN if [ "$SKIP_LINPERF" != "true" ]; then \
+ PKG_MGR=$(command -v dnf || command -v microdnf) && \
+ $PKG_MGR install -y procps-ng net-tools ncurses hostname && \
+ $PKG_MGR clean all; \
+ fi
COPY --chown=1001:0 src/main/liberty/config/ /config/
diff --git a/src/main/liberty/config/server.xml b/src/main/liberty/config/server.xml
index 7f92a611..118d712c 100644
--- a/src/main/liberty/config/server.xml
+++ b/src/main/liberty/config/server.xml
@@ -8,13 +8,16 @@
mpConfig-3.1
-
+
+
+
+
+
-
-
+
From 90fdd4e07b1600af037276be666e5438b1896806 Mon Sep 17 00:00:00 2001
From: Kirby Chin <37311900+kabicin@users.noreply.github.com>
Date: Mon, 17 Aug 2026 13:59:34 -0400
Subject: [PATCH 2/5] Temporary grant root access for installing packages
---
Dockerfile | 2 ++
1 file changed, 2 insertions(+)
diff --git a/Dockerfile b/Dockerfile
index f032decb..95ba9a86 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -17,11 +17,13 @@ LABEL \
description="This image contains a sample application that displays the Java system properties and demonstrates MicroProfile Config, Health and Metrics."
# Install required packages for running the Liberty MustGather (linperf.sh) script.
+USER 0
RUN if [ "$SKIP_LINPERF" != "true" ]; then \
PKG_MGR=$(command -v dnf || command -v microdnf) && \
$PKG_MGR install -y procps-ng net-tools ncurses hostname && \
$PKG_MGR clean all; \
fi
+USER 1001
COPY --chown=1001:0 src/main/liberty/config/ /config/
From e56916b3d0092e0fe7b83b8abf9034a5aecb1a21 Mon Sep 17 00:00:00 2001
From: Kirby Chin <37311900+kabicin@users.noreply.github.com>
Date: Mon, 17 Aug 2026 15:25:48 -0400
Subject: [PATCH 3/5] Add mpJwt config for mpMetrics
---
Dockerfile | 3 ++
dev/getting-started-pod.yaml | 87 ++++++++++++++++++++++++++++++
dev/keycloak-realm.json | 27 ++++++++++
dev/sso.xml | 21 ++++++++
docker-compose.yml | 37 +++++++++++++
src/main/liberty/config/server.xml | 5 +-
6 files changed, 179 insertions(+), 1 deletion(-)
create mode 100644 dev/getting-started-pod.yaml
create mode 100644 dev/keycloak-realm.json
create mode 100644 dev/sso.xml
create mode 100644 docker-compose.yml
diff --git a/Dockerfile b/Dockerfile
index 95ba9a86..752181fd 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -27,6 +27,9 @@ USER 1001
COPY --chown=1001:0 src/main/liberty/config/ /config/
+# Bundle the dev SSO for mpMetrics. You can override at deploy time by mounting a different sso.xml
+COPY --chown=1001:0 dev/sso.xml /config/configDropins/overrides/sso.xml
+
RUN features.sh
COPY --chown=1001:0 target/*.war /config/apps/
diff --git a/dev/getting-started-pod.yaml b/dev/getting-started-pod.yaml
new file mode 100644
index 00000000..4b810ed6
--- /dev/null
+++ b/dev/getting-started-pod.yaml
@@ -0,0 +1,87 @@
+---
+# Development Pod: Liberty app + Keycloak sidecar
+# Containers in the same Pod share localhost — Keycloak is reachable at localhost:8080.
+#
+# To deploy:
+# kubectl create configmap keycloak-realm --from-file=realm.json=dev/keycloak-realm.json
+# kubectl apply -f dev/getting-started-pod.yaml
+#
+# To get a token and hit /metrics (after port-forwarding):
+# kubectl port-forward pod/getting-started 9443:9443 8080:8080
+#
+# TOKEN=$(curl -s -X POST \
+# http://localhost:8080/realms/liberty/protocol/openid-connect/token \
+# -d "grant_type=password&client_id=metrics-client&client_secret=metrics-secret&username=metrics&password=metrics" \
+# | jq -r .access_token)
+#
+# curl -k -H "Authorization: Bearer $TOKEN" https://localhost:9443/metrics
+#
+# In production: remove the keycloak container and sso-override volume, and provide
+# a Secret containing MP_JWT_ISSUER + MP_JWT_JWKS_URI pointing at your real IdP.
+apiVersion: v1
+kind: Pod
+metadata:
+ name: getting-started
+ labels:
+ app: getting-started
+spec:
+ initContainers:
+ # Wait for Keycloak to finish importing the realm before Liberty starts
+ - name: wait-for-keycloak
+ image: curlimages/curl:8.8.0
+ command:
+ - sh
+ - -c
+ - |
+ until curl -sf http://localhost:8080/realms/liberty/.well-known/openid-configuration; do
+ echo "Waiting for Keycloak realm..."; sleep 5;
+ done
+
+ containers:
+ - name: keycloak
+ image: quay.io/keycloak/keycloak:25.0
+ args: ["start-dev", "--import-realm"]
+ env:
+ - name: KEYCLOAK_ADMIN
+ value: admin
+ - name: KEYCLOAK_ADMIN_PASSWORD
+ value: admin
+ volumeMounts:
+ - name: realm-config
+ mountPath: /opt/keycloak/data/import
+ ports:
+ - containerPort: 8080
+
+ - name: app
+ image: icr.io/appcafe/open-liberty/samples/getting-started:latest
+ volumeMounts:
+ # Mount the dev SSO include; in production replace with a real sso.xml via Secret/ConfigMap
+ - name: sso-override
+ mountPath: /config/configDropins/overrides/sso.xml
+ subPath: sso.xml
+ ports:
+ - containerPort: 9443
+
+ volumes:
+ - name: realm-config
+ configMap:
+ name: keycloak-realm
+ - name: sso-override
+ configMap:
+ name: liberty-sso-dev
+---
+# ConfigMap holding the dev sso.xml — points mpJwt at the Keycloak sidecar on localhost
+apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: liberty-sso-dev
+data:
+ sso.xml: |
+
+
+
+
+
diff --git a/dev/keycloak-realm.json b/dev/keycloak-realm.json
new file mode 100644
index 00000000..ff7b6a29
--- /dev/null
+++ b/dev/keycloak-realm.json
@@ -0,0 +1,27 @@
+{
+ "realm": "liberty",
+ "enabled": true,
+ "clients": [
+ {
+ "clientId": "metrics-client",
+ "enabled": true,
+ "publicClient": false,
+ "secret": "metrics-secret",
+ "directAccessGrantsEnabled": true,
+ "defaultClientScopes": ["profile", "email"]
+ }
+ ],
+ "users": [
+ {
+ "username": "metrics",
+ "enabled": true,
+ "credentials": [
+ {
+ "type": "password",
+ "value": "metrics",
+ "temporary": false
+ }
+ ]
+ }
+ ]
+}
diff --git a/dev/sso.xml b/dev/sso.xml
new file mode 100644
index 00000000..063a78d9
--- /dev/null
+++ b/dev/sso.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644
index 00000000..2ce345d7
--- /dev/null
+++ b/docker-compose.yml
@@ -0,0 +1,37 @@
+services:
+
+ keycloak:
+ image: quay.io/keycloak/keycloak:25.0
+ command: start-dev --import-realm
+ environment:
+ KEYCLOAK_ADMIN: admin
+ KEYCLOAK_ADMIN_PASSWORD: admin
+ volumes:
+ - ./dev/keycloak-realm.json:/opt/keycloak/data/import/realm.json:ro
+ ports:
+ - "8080:8080"
+ healthcheck:
+ test: ["CMD-SHELL", "curl -sf http://localhost:8080/realms/liberty/.well-known/openid-configuration || exit 1"]
+ interval: 10s
+ timeout: 5s
+ retries: 12
+
+ app:
+ build: .
+ volumes:
+ # Mount the dev SSO include file into the Liberty config includes directory
+ - ./dev/sso.xml:/config/configDropins/overrides/sso.xml:ro
+ ports:
+ - "9443:9443"
+ depends_on:
+ keycloak:
+ condition: service_healthy
+
+# To get a token and hit /metrics:
+#
+# TOKEN=$(curl -s -X POST \
+# http://localhost:8080/realms/liberty/protocol/openid-connect/token \
+# -d "grant_type=password&client_id=metrics-client&client_secret=metrics-secret&username=metrics&password=metrics" \
+# | jq -r .access_token)
+#
+# curl -k -H "Authorization: Bearer $TOKEN" https://localhost:9443/metrics
diff --git a/src/main/liberty/config/server.xml b/src/main/liberty/config/server.xml
index 118d712c..21dec806 100644
--- a/src/main/liberty/config/server.xml
+++ b/src/main/liberty/config/server.xml
@@ -6,13 +6,16 @@
mpMetrics-5.1
mpHealth-4.0
mpConfig-3.1
+
+ mpJwt-2.1
-
+
+
From 8b61ef53dee68a624dee1381449fdb74ca3b2749 Mon Sep 17 00:00:00 2001
From: Kirby Chin <37311900+kabicin@users.noreply.github.com>
Date: Tue, 18 Aug 2026 09:20:04 -0400
Subject: [PATCH 4/5] Add SSO sample with Open Liberty Operator
---
dev/getting-started-app.yaml | 68 ++++++++++++++++++++++++++++
dev/getting-started-pod.yaml | 87 ------------------------------------
dev/keycloak-realm.json | 17 +++++++
dev/sso.xml | 7 ++-
4 files changed, 88 insertions(+), 91 deletions(-)
create mode 100644 dev/getting-started-app.yaml
delete mode 100644 dev/getting-started-pod.yaml
diff --git a/dev/getting-started-app.yaml b/dev/getting-started-app.yaml
new file mode 100644
index 00000000..6acf08ef
--- /dev/null
+++ b/dev/getting-started-app.yaml
@@ -0,0 +1,68 @@
+---
+# Development OpenLibertyApplication: Liberty app + Keycloak sidecar
+# Containers in the same Pod share localhost — Keycloak is reachable at localhost:8080.
+#
+# Prerequisites:
+# oc create configmap keycloak-realm --from-file=realm.json=dev/keycloak-realm.json
+# oc apply -f dev/getting-started-app.yaml
+#
+# To hit the /metrics endpoint run:
+# ./dev/metrics.sh
+#
+# In production: remove sidecarContainers, and provide a Secret
+# containing the real sso.xml mounted at /config/configDropins/overrides/sso.xml.
+apiVersion: apps.openliberty.io/v1
+kind: OpenLibertyApplication
+metadata:
+ name: getting-started
+spec:
+ applicationImage: icr.io/appcafe/open-liberty/samples/getting-started:latest
+ replicas: 1
+
+ service:
+ port: 9443
+
+ # Dev Keycloak sidecar — shares localhost with the Liberty container
+ sidecarContainers:
+ - name: keycloak
+ image: quay.io/keycloak/keycloak:25.0
+ args: ["start-dev", "--import-realm"]
+ env:
+ - name: KEYCLOAK_ADMIN
+ value: admin
+ - name: KEYCLOAK_ADMIN_PASSWORD
+ value: admin
+ volumeMounts:
+ - name: realm-config
+ mountPath: /opt/keycloak/data/import
+ ports:
+ - containerPort: 8080
+
+ # Mount the dev sso.xml; in production replace with a Secret mount pointing at your real IdP
+ volumeMounts:
+ - name: sso-override
+ mountPath: /config/configDropins/overrides/sso.xml
+ subPath: sso.xml
+
+ volumes:
+ - name: realm-config
+ configMap:
+ name: keycloak-realm
+ - name: sso-override
+ configMap:
+ name: liberty-sso-dev
+---
+# ConfigMap holding the dev sso.xml — points mpJwt at the Keycloak sidecar on localhost
+apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: liberty-sso-dev
+data:
+ sso.xml: |
+
+
+
+
+
diff --git a/dev/getting-started-pod.yaml b/dev/getting-started-pod.yaml
deleted file mode 100644
index 4b810ed6..00000000
--- a/dev/getting-started-pod.yaml
+++ /dev/null
@@ -1,87 +0,0 @@
----
-# Development Pod: Liberty app + Keycloak sidecar
-# Containers in the same Pod share localhost — Keycloak is reachable at localhost:8080.
-#
-# To deploy:
-# kubectl create configmap keycloak-realm --from-file=realm.json=dev/keycloak-realm.json
-# kubectl apply -f dev/getting-started-pod.yaml
-#
-# To get a token and hit /metrics (after port-forwarding):
-# kubectl port-forward pod/getting-started 9443:9443 8080:8080
-#
-# TOKEN=$(curl -s -X POST \
-# http://localhost:8080/realms/liberty/protocol/openid-connect/token \
-# -d "grant_type=password&client_id=metrics-client&client_secret=metrics-secret&username=metrics&password=metrics" \
-# | jq -r .access_token)
-#
-# curl -k -H "Authorization: Bearer $TOKEN" https://localhost:9443/metrics
-#
-# In production: remove the keycloak container and sso-override volume, and provide
-# a Secret containing MP_JWT_ISSUER + MP_JWT_JWKS_URI pointing at your real IdP.
-apiVersion: v1
-kind: Pod
-metadata:
- name: getting-started
- labels:
- app: getting-started
-spec:
- initContainers:
- # Wait for Keycloak to finish importing the realm before Liberty starts
- - name: wait-for-keycloak
- image: curlimages/curl:8.8.0
- command:
- - sh
- - -c
- - |
- until curl -sf http://localhost:8080/realms/liberty/.well-known/openid-configuration; do
- echo "Waiting for Keycloak realm..."; sleep 5;
- done
-
- containers:
- - name: keycloak
- image: quay.io/keycloak/keycloak:25.0
- args: ["start-dev", "--import-realm"]
- env:
- - name: KEYCLOAK_ADMIN
- value: admin
- - name: KEYCLOAK_ADMIN_PASSWORD
- value: admin
- volumeMounts:
- - name: realm-config
- mountPath: /opt/keycloak/data/import
- ports:
- - containerPort: 8080
-
- - name: app
- image: icr.io/appcafe/open-liberty/samples/getting-started:latest
- volumeMounts:
- # Mount the dev SSO include; in production replace with a real sso.xml via Secret/ConfigMap
- - name: sso-override
- mountPath: /config/configDropins/overrides/sso.xml
- subPath: sso.xml
- ports:
- - containerPort: 9443
-
- volumes:
- - name: realm-config
- configMap:
- name: keycloak-realm
- - name: sso-override
- configMap:
- name: liberty-sso-dev
----
-# ConfigMap holding the dev sso.xml — points mpJwt at the Keycloak sidecar on localhost
-apiVersion: v1
-kind: ConfigMap
-metadata:
- name: liberty-sso-dev
-data:
- sso.xml: |
-
-
-
-
-
diff --git a/dev/keycloak-realm.json b/dev/keycloak-realm.json
index ff7b6a29..fec6432d 100644
--- a/dev/keycloak-realm.json
+++ b/dev/keycloak-realm.json
@@ -1,6 +1,21 @@
{
"realm": "liberty",
"enabled": true,
+ "requiredCredentials": ["password"],
+ "attributes": {
+ "userProfileEnabled": "false"
+ },
+ "requiredActions": [
+ {
+ "alias": "VERIFY_PROFILE",
+ "name": "Verify Profile",
+ "providerId": "VERIFY_PROFILE",
+ "enabled": false,
+ "defaultAction": false,
+ "priority": 90,
+ "config": {}
+ }
+ ],
"clients": [
{
"clientId": "metrics-client",
@@ -15,6 +30,8 @@
{
"username": "metrics",
"enabled": true,
+ "emailVerified": true,
+ "requiredActions": [],
"credentials": [
{
"type": "password",
diff --git a/dev/sso.xml b/dev/sso.xml
index 063a78d9..f145b6ab 100644
--- a/dev/sso.xml
+++ b/dev/sso.xml
@@ -1,5 +1,5 @@
+ jwksUri="${MP_JWT_JWKS_URI}"/>
From a9a49cd091f3a6f506b7e860d936993de1e10322 Mon Sep 17 00:00:00 2001
From: Kirby Chin <37311900+kabicin@users.noreply.github.com>
Date: Tue, 18 Aug 2026 09:24:20 -0400
Subject: [PATCH 5/5] Create metrics.sh
---
dev/metrics.sh | 75 ++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 75 insertions(+)
create mode 100755 dev/metrics.sh
diff --git a/dev/metrics.sh b/dev/metrics.sh
new file mode 100755
index 00000000..d43d9905
--- /dev/null
+++ b/dev/metrics.sh
@@ -0,0 +1,75 @@
+#!/usr/bin/env bash
+# dev/metrics.sh — fetch the mpMetrics endpoint from the getting-started pod
+#
+# Usage:
+# ./dev/metrics.sh # all metrics (default)
+# ./dev/metrics.sh /metrics/base # base metrics
+# ./dev/metrics.sh /metrics/application # application metrics
+
+set -uo pipefail # no -e: port-forward drops are handled explicitly
+
+METRICS_PATH="${1:-/metrics}"
+LABEL="app.kubernetes.io/name=getting-started"
+LIBERTY_PORT=9443
+KEYCLOAK_PORT=8080
+PF_PID=""
+
+cleanup() { [[ -n "$PF_PID" ]] && kill "$PF_PID" 2>/dev/null; }
+trap cleanup EXIT
+
+start_portforward() {
+ [[ -n "$PF_PID" ]] && kill "$PF_PID" 2>/dev/null
+ oc port-forward "$POD" "${LIBERTY_PORT}:${LIBERTY_PORT}" "${KEYCLOAK_PORT}:${KEYCLOAK_PORT}" \
+ 2>/dev/null &
+ PF_PID=$!
+}
+
+# ── 1. Resolve pod name ──────────────────────────────────────────────────────
+POD=$(oc get pod -l "$LABEL" -o jsonpath='{.items[0].metadata.name}' 2>/dev/null)
+if [[ -z "$POD" ]]; then
+ echo "ERROR: no pod found with label $LABEL" >&2
+ exit 1
+fi
+echo "Using pod: $POD"
+
+# ── 2. Wait for Keycloak container to be ready ───────────────────────────────
+echo "Waiting for Keycloak container to be ready..."
+oc wait pod "$POD" --for=condition=Ready --timeout=300s 2>/dev/null || true
+
+# ── 3. Port-forward and wait for Keycloak OIDC endpoint ─────────────────────
+echo "Waiting for Keycloak realm..."
+start_portforward
+for i in $(seq 1 90); do
+ # Restart port-forward if it died
+ if ! kill -0 "$PF_PID" 2>/dev/null; then
+ sleep 3
+ start_portforward
+ fi
+ curl -sf "http://localhost:${KEYCLOAK_PORT}/realms/liberty/.well-known/openid-configuration" \
+ -o /dev/null 2>/dev/null && break
+ sleep 3
+done
+
+# ── 4. Obtain JWT from Keycloak ──────────────────────────────────────────────
+echo "Obtaining token..."
+TOKEN=$(curl -sf -X POST \
+ "http://localhost:${KEYCLOAK_PORT}/realms/liberty/protocol/openid-connect/token" \
+ -d "grant_type=password&client_id=metrics-client&client_secret=metrics-secret&username=metrics&password=metrics" \
+ | jq -r .access_token)
+
+if [[ -z "$TOKEN" ]] || [[ "$TOKEN" == "null" ]]; then
+ echo "ERROR: failed to obtain token — check Keycloak logs with:" >&2
+ echo " oc logs $POD -c keycloak | grep -i error" >&2
+ exit 1
+fi
+echo "Token obtained."
+
+# ── 5. Fetch metrics ─────────────────────────────────────────────────────────
+echo ""
+echo "GET https://localhost:${LIBERTY_PORT}${METRICS_PATH}"
+echo "────────────────────────────────────────────────────"
+HTTP_CODE=$(curl -sk -o /tmp/metrics_response.txt -w "%{http_code}" \
+ -H "Authorization: Bearer $TOKEN" \
+ "https://localhost:${LIBERTY_PORT}${METRICS_PATH}")
+echo "HTTP $HTTP_CODE"
+cat /tmp/metrics_response.txt