Ping Identity Devops

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-devops Helm chart version 0.14.0 or later (this guide was validated on 0.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-flex example 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 flex-bootstrap sidecar performs both actions over the Admin API after PingFederate starts, mirroring what the legacy image’s post-start hooks did. It waits up to 900 seconds for the admin API — a budget tied to the examples' startup probe window.

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.0 tie StatefulSet persistence to StatefulSet workloads only; the PingFederate admin defaults to a Deployment).

  • Remaps the PVC from the chart’s default /opt/out mount to exactly /opt/pingidentity/runtime/server/default/data — the single durable path the flex image actually writes (the flex image has no /opt/out convention).

  • 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-data init container copies the seed in exactly once; later pods leave existing data intact.

  • Keeps profile staging in a separate ephemeral emptyDir that 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 /opt/pingidentity/runtime/server/default/data — not the v1 /opt/out/instance/server/default/data path. The image ships that directory pre-populated with its base configuration seed, so mounting an empty volume at the path also breaks startup ("Unable to locate configuration list value …​"). Mount the PVC at the runtime data path as the examples do, and seed it on first start (the examples' seed-pvc-data init container handles this).

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 with subPath. (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-data init container, for the same reason as Example 5.

  • Delivers scalar settings as environment variables (PF_RUN_<property> for run.properties settings, 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:

  1. Inventory every file the profile delivers and classify each as a scalar setting, a required filesystem artifact, or obsolete.

  2. Move supported scalar values to environment variables in the container.env/envs blocks; the chart renders them into the product env-vars ConfigMap.

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

  4. 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 volumeMounts entry per file. mountPath is the same path under /opt/pingidentity/runtime; subPath mirrors 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.zip itself: PingFederate renames the file to data.zip.deployed after import, which fails if the file is a mount point.

  • The init container must create every subPath source 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 jgroups.properties must match your release name and namespace exactly. Create the pingfederate-cluster-config ConfigMap with the helper script before installing a clustered example:

./create-cluster-config.sh <release-name> <namespace>

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

  1. Inventory current image tags, Helm chart version, server-profile repository and ref, hook scripts, mounted paths, persisted data, Secrets, and OpenShift constraints.

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

  3. If needed, begin with the full-profile bridge example and keep durable data on its separate PVC.

  4. Validate startup, health probes, licensing, bootstrap, configuration, clustering (if applicable), and persistence.

  5. Reduce the profile: move supported scalar values to environment variables and identify only the required filesystem artifacts.

  6. 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: 90 x periodSeconds: 10). If you change the probe, update the retry budget in flex-bootstrap.sh to 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 uses pingfederate-admin-creds).

  • Clustered admin consoles all report ACTIVE. DNS_PING discovery is not resolving peers. Recreate the pingfederate-cluster-config ConfigMap with the exact release name and namespace, then restart the pods.

  • Engine pods never become ready in clustered mode. Set RUN_PF_OPERATIONAL_MODE on 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.zip file, 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-flex at 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.

Cleaning up

helm uninstall pf-flex -n ping

Deleting the namespace also deletes the Secrets, ConfigMap, and (subject to the StorageClass reclaim policy) the PVC created during the walkthrough.