Migrating PingFederate Deployments to Flex Images
This walkthrough expands the Flex Images overview into a complete sample migration for PingFederate using the ping-devops Helm chart and the PingFederate flex example values in the pingidentity-devops-getting-started repository.
Before you begin
PingFederate flex images use a simplified container runtime model designed for modern orchestration platforms. They reduce reliance on image startup hooks and runtime file manipulation, and support configuration through environment variables and selected file mounts.
Flex images are not drop-in replacements for the current PingFederate image line. Existing deployments may depend on startup hooks, automatic server-profile retrieval, profile overlays, or filesystem assumptions that differ in flex. Plan and validate a deliberate migration above and beyond simply changing the image tag. Current and flex image lines can coexist during migration (the same product code will be operating in each set); select and pin the image tag that you have tested.
|
The example values used in this walkthrough are for demonstration and are not suitable for production use without modification. Validate startup, probes, licensing, persistence, and clustering in a non-production environment before cutover. See Support boundaries. |
Prerequisites
-
A Kubernetes cluster and Helm 3+
-
ping-devopsHelm chart version0.14.0or later (this guide was validated on0.16.0). Charts require explicit flex probe commands and literal Secret/ConfigMap names in sidecars. -
A valid PingFederate license file. Flex images do not use the DevOps license-server flow; the license is delivered as a file mounted at
/opt/staging/license. -
For Examples 5 and 6 from the getting-started repository, a default StorageClass capable of ReadWriteOnce volumes (the admin console requests a 4Gi PVC).
-
The
30-helm/pingfederate-flexexample directory checked out locally.
If necessary, add the chart repository:
helm repo add pingidentity https://helm.pingidentity.com/
helm repo update
Common setup
Create the namespace, Secrets, and ConfigMap that every flex example consumes. Run these commands from the 30-helm/pingfederate-flex directory.
kubectl create namespace ping
# DevOps secret: used by Example 5's bootstrap sidecar, and carries the
# PING_IDENTITY_ACCEPT_EULA setting over from the v1 pattern.
kubectl create secret generic devops-secret -n ping \
--from-literal=PING_IDENTITY_ACCEPT_EULA='YES' \
--from-literal=PING_IDENTITY_PASSWORD='<admin-password>'
# Credential secret: used by Example 6's bootstrap sidecar.
kubectl create secret generic pingfederate-admin-creds -n ping \
--from-literal=PING_IDENTITY_PASSWORD='<admin-password>'
# License secret: mounted at /opt/staging/license.
kubectl create secret generic pingfederate-license -n ping \
--from-file=license=./pingfederate.lic
# Bootstrap script ConfigMap: mounted into the flex-bootstrap sidecar.
kubectl create configmap flex-bootstrap-script -n ping \
--from-file=flex-bootstrap.sh=./flex-bootstrap.sh
|
Flex images ship without the former hook-script system, so there is no mechanism in the image that accepts the license agreement or creates the initial administrator. The |
Choose a migration path
Path A: bridge an existing full server profile
Use the full-profile staging pattern when you need to keep an existing Git-hosted server profile while beginning the migration. An init container stages the profile before PingFederate starts. For deployments that persist runtime data, keep the durable PVC separate from temporary profile staging and mount it only at the intended data path.
This is a transitional compatibility pattern, not the preferred end state: staging the full runtime and overlaying a full profile can mask the image runtime and couple profile contents to the image tag. Keep the image and profile version-aligned, and plan to reduce the profile over time.
Start with Example 5: server profile with PVC. For a deployment without a PVC, see Examples 3 and 4 in the example directory.
What Example 5 does:
-
Runs the admin console as a StatefulSet so chart-generated persistence is available (charts at least through
0.16.0tie StatefulSet persistence to StatefulSet workloads only; the PingFederate admin defaults to a Deployment). -
Remaps the PVC from the chart’s default
/opt/outmount to exactly/opt/pingidentity/runtime/server/default/data— the single durable path the flex image actually writes (the flex image has no/opt/outconvention). -
Seeds the PVC on first start: the flex image ships a populated data-directory seed (base config store, embedded database), and an empty PVC mounted at that path hides the seed and prevents startup. A
seed-pvc-datainit container copies the seed in exactly once; later pods leave existing data intact. -
Keeps profile staging in a separate ephemeral
emptyDirthat is rebuilt on every pod creation, while the PVC survives pod deletion and Helm upgrades.
Install it:
helm install pf-flex pingidentity/ping-devops -n ping \
-f 05-server-profile-pvc.yaml
Verify the deployment
Use the same checks for Path A and Path B. The following assumes the release name pf-flex; adjust for your release.
# Both admin and engine pods reach Running.
kubectl get pods -n ping -l app.kubernetes.io/instance=pf-flex
# The bootstrap sidecar reports completion.
kubectl logs -n ping sts/pf-flex-pingfederate-admin -c flex-bootstrap --tail=5
# Expected final line: Bootstrap complete.
# Heartbeat responds on the admin port.
kubectl port-forward -n ping sts/pf-flex-pingfederate-admin 9999:9999
# In a second shell:
curl -sk https://localhost:9999/pf/heartbeat.ping
Sign in to the admin console at https://localhost:9999 as the bootstrap administrator to confirm licensing and initial configuration.
Confirm persistence for the PVC examples:
kubectl get pvc -n ping
kubectl exec -n ping sts/pf-flex-pingfederate-admin -- \
ls /opt/pingidentity/runtime/server/default/data
The data directory exists on first start and holds the runtime data PingFederate writes (key material, config-store/, the embedded database); it persists across pod deletion and helm upgrade.
|
Flex images keep all durable state under |
Path B: move to a minimal, flex-native profile
Move scalar settings into supported environment variables and retain only files that cannot be represented through environment variables. Stage those files separately and mount only the individual files needed by the product. Do not mount the profile over the flex image runtime directory: a directory mount can hide files supplied by the image.
For deployments with persistent runtime data, use Example 6: minimal profile with PVC. The PVC remains at /opt/pingidentity/runtime/server/default/data; profile staging is temporary and rebuilt when a pod is created. For deployments without a PVC, see Example 4 in the example directory.
What Example 6 does:
-
Does not mount anything over the image’s
/opt/pingidentity/runtime— the image stays authoritative for the runtime, and only verified profile artifacts are delivered as individual file mounts withsubPath. (The one exception is the durable data directory: the PVC is mounted there and seeded on first start, so runtime state persists.) -
Seeds the PVC data directory exactly once via a
seed-pvc-datainit container, for the same reason as Example 5. -
Delivers scalar settings as environment variables (
PF_RUN_<property>forrun.propertiessettings,PF_<FILE>_<property>for other config files), rendered by the chart into its env-vars ConfigMap. -
Pins the profile Git ref to a commit and asserts it before staging, so pod creation fails fast if the pin no longer matches.
Install it with the same release name after deleting the Path A release, or into a separate namespace for comparison:
helm install pf-flex pingidentity/ping-devops -n ping \
-f 06-minimal-server-profile-pvc.yaml
Then repeat the checks in Verify the deployment.
Private profile repositories
The staging init container in Example 6 clones the profile repository with Git. For a private repository, create a credential Secret and pass it to the init container:
kubectl create secret generic pf-profile-git -n ping \
--from-literal=username='<git-username>' \
--from-literal=password='<git-token>'
Uncomment the GIT_USERNAME/GIT_PASSWORD environment entries in the example’s stage-server-profile init container, set GIT_TERMINAL_PROMPT=0, and configure a Git credential helper that reads those variables. Keep the SERVER_PROFILE_REF pinned to a commit so staging stays deterministic.
What changes from the legacy model
-
Flex images do not use the legacy hook system. Work previously performed by hooks — such as staging configuration or post-start bootstrap — must be handled through the documented flex deployment pattern.
-
Flex does not automatically retrieve and merge a Git-hosted server profile through the legacy
SERVER_PROFILE_*startup flow. Use an init container or another supported staging method, then deliver only the required artifacts. -
Durable application/runtime data must be kept distinct from ephemeral configuration staging. Do not use the staging volume as the persistence mechanism.
-
Flex-specific paths and health probes shown in the examples (
/opt/pingidentity/runtime/bin/liveness.sh) will vary from the previous model. See the example README prerequisites for the Helm chart, license, credentials, ConfigMaps, and storage.
Reducing the profile
Once the bridge (Path A) is stable, shrink the profile toward the Path B target:
-
Inventory every file the profile delivers and classify each as a scalar setting, a required filesystem artifact, or obsolete.
-
Move supported scalar values to environment variables in the
container.env/envsblocks; the chart renders them into the product env-vars ConfigMap. -
Keep only files that cannot be represented as environment variables (for example, drop-in deployer artifacts or custom XML) and deliver each as an individual file mount.
-
Update the staging init container to copy only those files, and extend its assertions (
test -f,test -s) so staging fails fast on drift.
File-mounting rules:
-
One
volumeMountsentry per file.mountPathis the same path under/opt/pingidentity/runtime;subPathmirrors the repository path. -
Never mount a directory over image-populated directories (
conf/,bin/,lib/) — a directory mount hides the image’s own files. Directory mounts are safe only where the image directory is empty. -
For drop-in deployer artifacts, mount the drop-in-deployer directory, not
data.zipitself: PingFederate renames the file todata.zip.deployedafter import, which fails if the file is a mount point. -
The init container must create every
subPathsource before the main container starts.
Clustered deployments
The examples use standalone operation. For clustered consoles and engines, see Example 2 and Example 4, which deliver jgroups.properties (DNS_PING discovery) and cluster-admin-nodes-sync.conf as files — environment-variable overrides do not work on either file in flex.
|
The admin cluster Service FQDN in
Without working DNS_PING discovery, every admin pod self-promotes to ACTIVE (split-brain). |
In clustered mode, engine containers additionally need the uppercase RUN_PF_OPERATIONAL_MODE variable, which the flex liveness.sh reads to select the engine heartbeat port (see Example 4).
OpenShift and Helm considerations
Use the OpenShift overlay and deployment settings documented with the example values — it removes the chart’s hardcoded runAsUser/fsGroup values that OpenShift rejects. Add -f openshift-overlay.yaml to the install command alongside the main example values file.
Validate the security context, file ownership and permissions, startup command, probes, Secrets, PVC behavior, and profile staging in a non-production environment before cutover. Do not assume values that worked with a legacy image behave identically with flex.
The Ping Helm charts through at least 0.16.0 do not render sidecar values through templating, so keep Secret and ConfigMap names in sidecar blocks literal. They also hardcode OPERATIONAL_MODE in the product env ConfigMap, which is why the examples set both PF_RUN_pf_operational_mode and OPERATIONAL_MODE.
Suggested migration sequence
-
Inventory current image tags, Helm chart version, server-profile repository and ref, hook scripts, mounted paths, persisted data, Secrets, and OpenShift constraints.
-
Pin a flex image version and test it in a non-production environment; keep the existing image deployment available for rollback under its own release name.
-
If needed, begin with the full-profile bridge example and keep durable data on its separate PVC.
-
Validate startup, health probes, licensing, bootstrap, configuration, clustering (if applicable), and persistence.
-
Reduce the profile: move supported scalar values to environment variables and identify only the required filesystem artifacts.
-
Switch to the minimal-profile example, verify upgrades and rollback behavior, then promote through the normal release process.
Do not point a flex deployment and the legacy deployment at the same PVC. The examples create a new PVC for the flex release; moving existing runtime data into it is deployment-specific and should be validated separately.
Troubleshooting
-
Bootstrap sidecar gives up while PingFederate is still starting. The sidecar waits 900 seconds, matched to the startup probe window (
failureThreshold: 90xperiodSeconds: 10). If you change the probe, update the retry budget inflex-bootstrap.shto match. -
Administrator creation returns 422 repeatedly. PingFederate can report the heartbeat before the administrative-account resource finishes initializing; the sidecar retries this transient response. Persistent non-422 errors indicate a real failure — inspect the Admin API response.
-
Administrator creation returns 401. The credential Secret’s password does not match, or the administrator already exists with different credentials. The sidecar verifies the configured credentials before declaring success; check the Secret the sidecar actually reads (Example 5 uses
devops-secret, Example 6 usespingfederate-admin-creds). -
Clustered admin consoles all report ACTIVE. DNS_PING discovery is not resolving peers. Recreate the
pingfederate-cluster-configConfigMap with the exact release name and namespace, then restart the pods. -
Engine pods never become ready in clustered mode. Set
RUN_PF_OPERATIONAL_MODEon the engine container so the flex liveness script selects the engine heartbeat port. -
A drop-in artifact fails to import. Mount the drop-in-deployer directory rather than the
data.zipfile, which PingFederate renames after import. -
Configuration files appear to be missing after an image upgrade. It is possible a directory mount is shadowing image-supplied files; replace it with individual file mounts.
Support boundaries
-
The example values are demonstrations and may need modification before production use. Confirm the sample image tag (
13.1.4-alpine-flexat the time of writing), the chart-version floor, and the OpenShift overlay instructions against the current released examples and the chart release notes before adopting them. -
Product-version-specific guidance about where supported settings belong — and which files must remain in the profile — follows the PingFederate product documentation for your version.
-
Distinguish image behavior changes from deployment misconfiguration when requesting support: hook absence, probe paths, and profile staging are image model changes; split-brain, mount shadowing, and probe budget mismatches are configuration issues addressed above.
-
This guidance describes a general migration pattern. It does not promise specific vulnerability reductions or universal compatibility for every deployment.