Provisioning with static storage configuration
From IBM Cloud PakĀ® foundational services version 4.7.0 and later, you can install foundational services with static storage configuration. Use static provisioning when you want complete control over a storage resource, including its exact size, type, and location, or in regulated environments where storage must be pre-approved, audited, and conformed to security policies.
Setting up static storage provisioning with a Network File System (NFS) server
- Create a Red Hat OpenShift Container Platform cluster.
- Collect the storage server and path.
cat /etc/exports- The following example shows the default NFS server on an OpenShift Container Platform cluster:
nfs: server: <server address> 000.00.00.000 # Get the NFS server IP with the following command: # oc get svc nfs-server -n nfs-server -o jsonpath='{.spec.clusterIP}' - To collect the path, enter the
path: /exportsexport path.
- The following example shows the default NFS server on an OpenShift Container Platform cluster:
- Set up the NFS server.
- Create export directories for each persistent volume (PV).
mkdir -p /exports/common-service-db-1 mkdir -p /exports/common-service-db-1-wal mkdir -p /exports/common-service-db-2 mkdir -p /exports/common-service-db-2-wal - Set permissions.
chmod 700 /exports/common-service-db-* chown -R 26:26 /exports/common-service-db-* - Configure and reload exports.
cat > /etc/exports << "EXPORTS" /exports *(rw,sync,fsid=0,crossmnt,no_subtree_check,no_root_squash,insecure) /exports/common-service-db-1 *(rw,sync,no_subtree_check,no_root_squash,insecure) /exports/common-service-db-1-wal *(rw,sync,no_subtree_check,no_root_squash,insecure) /exports/common-service-db-2 *(rw,sync,no_subtree_check,no_root_squash,insecure) /exports/common-service-db-2-wal *(rw,sync,no_subtree_check,no_root_squash,insecure) EXPORTS exportfs -ra- The
fsid=0value on the/exportspath makes this path the Network File System version 4 (NFSv4) pseudo-root. - The
crossmntoption allows for crossing into subdirectories. - Client paths automatically remove the
/exportsprefix.
- The
- Create export directories for each persistent volume (PV).
- Predefine four PVs to support two
common-service-dbpods:- Predefine the
common-service-db-1PV with a data volume of 20 GiB. - Predefine the
common-service-db-1-walPV with a Write-Ahead Logging (WAL) volume of 10 GiB. - Predefine the
common-service-db-2PV with a data volume of 20 GiB. - Predefine the
common-service-db-2-walPV with a WAL volume of 10 GiB.
The following example shows a production-ready PV configuration:apiVersion: v1 kind: PersistentVolume metadata: name: common-service-db-1 spec: capacity: storage: 20Gi accessModes: - ReadWriteOnce persistentVolumeReclaimPolicy: Retain nfs: server: <nfs-server address> # for example, 000.00.00.000 path: /common-service-db-1 mountOptions: - hard - nfsvers=4.1 --- apiVersion: v1 kind: PersistentVolume metadata: name: common-service-db-1-wal spec: capacity: storage: 10Gi accessModes: - ReadWriteOnce persistentVolumeReclaimPolicy: Retain nfs: server: <nfs-server address> # for example, 000.00.00.000 path: /common-service-db-1-wal mountOptions: - hard - nfsvers=4.1 --- apiVersion: v1 kind: PersistentVolume metadata: name: common-service-db-2 spec: capacity: storage: 20Gi accessModes: - ReadWriteOnce persistentVolumeReclaimPolicy: Retain nfs: server: <nfs-server address> # for example, 000.00.00.000 path: /common-service-db-2 mountOptions: - hard - nfsvers=4.1 --- apiVersion: v1 kind: PersistentVolume metadata: name: common-service-db-2-wal spec: capacity: storage: 10Gi accessModes: - ReadWriteOnce persistentVolumeReclaimPolicy: Retain nfs: server: <nfs-server address> # for example, 000.00.00.000 path: /common-service-db-2-wal mountOptions: - hard - nfsvers=4.1- The following mount option configurations are available:
- For databases, set the
hardoption so that NFS operations retry indefinitely on server failure. - Set the
nfsvers=4.1option to use NFSv4.1 for improved performance and security.
- For databases, set the
- The following reclaim policy configurations are available:
- For production, include the
Retainoption to preserve data after Persistent Volume Claim (PVC) deletion. This reclaim policy keeps the underlying storage even after the PVC is deleted so the data can be recovered or manually cleaned up. - Use the
Deleteoption to automatically delete PV data when a PVC is deleted. Use this reclaim policy only when the underlying storage is disposable. Deleting the PVC permanently removes the backing volume.
- For production, include the
- The following NFS export option configurations are available:
- The
no_root_squashoption allows root user access from NFS clients. - The
insecureoption allows connections from ports greater than 1024. - The
no_subtree_checkoption improves reliability by disabling subtree checking. - The
syncoption helps ensure that data is written to the disk before writes are acknowledged.
- The
The following example shows a simple PV:kind: PersistentVolume apiVersion: v1 metadata: name: static-storage-pv-11-new spec: capacity: storage: 10Gi nfs: server: 00.00.0.000 path: /data accessModes: - ReadWriteOnce claimRef: # Optional: Bind to an existing PVC kind: PersistentVolumeClaim namespace: <namespace> name: <pvc-name> persistentVolumeReclaimPolicy: Delete volumeMode: FilesystemThe following example shows a Rook Ceph PV:kind: PersistentVolume apiVersion: v1 metadata: name: pv spec: capacity: storage: 10Gi csi: driver: <csi-driver.example.com> volumeHandle: <unique-volume-id> fsType: ext4 volumeAttributes: # Optional: generic key-value attributes clusterID: rook-ceph imageFeatures: layering imageFormat: '2' imageName: <image-name> journalPool: replicapool pool: replicapool storage.kubernetes.io/csiProvisionerIdentity: <provisioner-identity> nodeStageSecretRef: # Optional: If the Container Storage Interface (CSI) driver requires secrets name: <csi-node-secret> namespace: <namespace> controllerExpandSecretRef: name: <csi-expand-secret> namespace: <namespace> accessModes: - ReadWriteOnce claimRef: # Optional: Bind to an existing PVC kind: PersistentVolumeClaim namespace: <namespace> name: <pvc-name> persistentVolumeReclaimPolicy: Delete volumeMode: FilesystemThe following example shows an OpenShift Container Platform Data Foundation (ODF) PV:kind: PersistentVolume apiVersion: v1 metadata: name: pv spec: capacity: storage: 10Gi csi: driver: <csi-driver.example.com> volumeHandle: <unique-volume-id> fsType: ext4 volumeAttributes: # Optional: generic key-value attributes clusterID: openshift-storage imageFeatures: 'layering,deep-flatten,exclusive-lock,object-map,fast-diff' imageFormat: '2' imageName: <image-name> journalPool: ocs-storagecluster-cephblockpool pool: ocs-storagecluster-cephblockpool storage.kubernetes.io/csiProvisionerIdentity: <provisioner-identity> nodeStageSecretRef: # Optional: If the CSI driver requires secrets name: <csi-node-secret> namespace: <namespace> controllerExpandSecretRef: name: <csi-expand-secret> namespace: <namespace> accessModes: - ReadWriteOnce claimRef: # Optional: Bind to an existing PVC kind: PersistentVolumeClaim namespace: <namespace> name: <pvc-name> persistentVolumeReclaimPolicy: Delete storageClassName: "" volumeMode: FilesystemThe following example shows a Portworx PV:apiVersion: v1 kind: PersistentVolume metadata: name: testvol spec: capacity: storage: 10Gi claimRef: # Optional: Bind to an existing PVC apiVersion: v1 kind: PersistentVolumeClaim name: <pvc-name> namespace: <namespace> accessModes: - ReadWriteOnce persistentVolumeReclaimPolicy: Retain portworxVolume: volumeID: "<px-volume-id>" - Predefine the
- Wait for the foundational services pods to run.
- Install ZenService. A storage class needs to be available to install ZenService. Install a storage class, such as Rook Ceph, NFS, ODF, or Portworx. Then, wait for the ZenService pods to run.
Setting up static storage provisioning with Rook Ceph
- Create a Red Hat OpenShift Container Platform cluster and install Rook Ceph.
- Collect
rook-cephfsinformation.- Run the following command to get the Ceph File System (CephFS) name:
oc exec -n rook-ceph deploy/rook-ceph-tools -- ceph fs lsThe output resembles the following example:name: myfs, metadata pool: myfs-metadata, data pools: [myfs-data0 ] - Run one of the following commands to get Ceph monitor endpoints or the cluster ID.
Get the Ceph monitor endpoints:
oc get cm -n rook-ceph rook-ceph-mon-endpoints -o jsonpath='{.data.data}' | jqGet the cluster ID:oc get cephcluster -n rook-ceph rook-ceph -o jsonpath='{.status.ceph.fsid}' - Run the following command to verify that the Container Storage Interface (CSI) secrets exist:
oc get secret -n rook-ceph | grep csi-cephfsThe
rook-csi-cephfs-nodeandrook-csi-cephfs-provisionersecrets are expected.
- Run the following command to get the Ceph File System (CephFS) name:
- Create CephFS subvolumes.
- Access the Rook Ceph toolbox with the following command:
oc exec -it -n rook-ceph deploy/rook-ceph-tools -- bash - Create subvolumes for the
common-service-dbPostgreSQL cluster. First, set the file system name:export FS_NAME=myfs # The name is included in the output from the command to get the CephFS name.Create subvolumes for PostgreSQL data and WAL:ceph fs subvolume create ${FS_NAME} common-service-db-1 ceph fs subvolume create ${FS_NAME} common-service-db-1-wal ceph fs subvolume create ${FS_NAME} common-service-db-2 ceph fs subvolume create ${FS_NAME} common-service-db-2-walVerify that the subvolumes were created:ceph fs subvolume ls ${FS_NAME}Get the subvolume paths and save them for PV creation:
ceph fs subvolume getpath ${FS_NAME} common-service-db-1 ceph fs subvolume getpath ${FS_NAME} common-service-db-1-wal ceph fs subvolume getpath ${FS_NAME} common-service-db-2 ceph fs subvolume getpath ${FS_NAME} common-service-db-2-walThe output resembles the following example:/volumes/_nogroup/common-service-db-1/uuid-here /volumes/_nogroup/common-service-db-1-wal/uuid-here /volumes/_nogroup/common-service-db-2/uuid-here /volumes/_nogroup/common-service-db-2-wal/uuid-here - Set size limits, called storage quotas, for subvolumes. Specify the size in bytes or with the proper unit, such as 21474836480 for 20 GB and 10737418240 for 10 GB.
ceph fs subvolume resize ${FS_NAME} common-service-db-1 21474836480 ceph fs subvolume resize ${FS_NAME} common-service-db-1-wal 10737418240 ceph fs subvolume resize ${FS_NAME} common-service-db-2 21474836480 ceph fs subvolume resize ${FS_NAME} common-service-db-2-wal 10737418240Verify that the quotas were set by checking thebytes_quotafield:ceph fs subvolume info ${FS_NAME} common-service-db-1 ceph fs subvolume info ${FS_NAME} common-service-db-1-wal ceph fs subvolume info ${FS_NAME} common-service-db-2 ceph fs subvolume info ${FS_NAME} common-service-db-2-walThe output resembles the following example:bash-4.4$ ceph fs subvolume resize ${FS_NAME} common-service-db-1 21474836480 [ { "bytes_used": 0 }, { "bytes_quota": 21474836480 }, { "bytes_pcent": "0.00" } ] bash-4.4$ ceph fs subvolume info ${FS_NAME} common-service-db-1 { "atime": "2026-05-22 19:47:06", "bytes_pcent": "0.00", "bytes_quota": 21474836480, "bytes_used": 0, "created_at": "2026-05-22 19:47:06", "ctime": "2026-05-22 19:52:44", "data_pool": "myfs-replicated", "features": [ "snapshot-clone", "snapshot-autoprotect", "snapshot-retention" ], "gid": 0, "mode": 16877, "mon_addrs": [ "172.30.151.57:6789", "172.30.18.59:6789", "172.30.7.232:6789" ], "mtime": "2026-05-22 19:47:06", "path": "/volumes/_nogroup/common-service-db-1/3691a72e-3932-4362-96ee-18d706539135", "pool_namespace": "", "state": "complete", "type": "subvolume", "uid": 0 } - Exit the toolbox with the
exitcommand.
- Access the Rook Ceph toolbox with the following command:
- Create a custom secret for static volumes so that the Rook operator doesn't overwrite the default
rook-csi-cephfs-nodesecret.- Get existing admin credentials from the secret that Rook manages:
export ADMIN_ID=$(oc get secret rook-csi-cephfs-node -n rook-ceph -o jsonpath='{.data.adminID}' | base64 -d) export ADMIN_KEY=$(oc get secret rook-csi-cephfs-node -n rook-ceph -o jsonpath='{.data.adminKey}' | base64 -d) - Display the values for verification:
echo "Admin ID: ${ADMIN_ID}" echo "Admin Key: ${ADMIN_KEY}" - Create a custom secret with all required fields:
oc create secret generic cephfs-static-secret \ --from-literal=userID="${ADMIN_ID}" \ --from-literal=userKey="${ADMIN_KEY}" \ --from-literal=adminID="${ADMIN_ID}" \ --from-literal=adminKey="${ADMIN_KEY}" \ -n rook-ceph - Verify that the secret was created correctly:
oc get secret cephfs-static-secret -n rook-ceph -o yaml
The output resembles the following example:apiVersion: v1 kind: Secret metadata: name: cephfs-static-secret namespace: rook-ceph type: Opaque data: adminID: Y3NpLWNlcGhmcy1ub2Rl adminKey: QVFCbFN1SnB1UlJzRUJBQTBmcXFyQ1ZRV3A2M0plS0Z5MUNMOVE9PQ== userID: Y3NpLWNlcGhmcy1ub2Rl userKey: QVFCbFN1SnB1UlJzRUJBQTBmcXFyQ1ZRV3A2M0plS0Z5MUNMOVE9PQ==- The Rook operator does not manage this custom secret.
- This custom secret persists across pod restarts and operator reconciliations.
- Use this secret in your PV definitions in the following step.
- Get existing admin credentials from the secret that Rook manages:
- Create a predefined PV pool.
- Create a YAML file with all PVs. Four PVs are defined to support two
common-service-dbpods that are running. Export the following variable:export DB1_PATH=$(oc exec -n ${NAMESPACE} deploy/rook-ceph-tools -- \ ceph fs subvolume getpath ${FS_NAME} common-service-db-1) export DB1_WAL_PATH=$(oc exec -n ${NAMESPACE} deploy/rook-ceph-tools -- \ ceph fs subvolume getpath ${FS_NAME} common-service-db-1-wal) export DB2_PATH=$(oc exec -n ${NAMESPACE} deploy/rook-ceph-tools -- \ ceph fs subvolume getpath ${FS_NAME} common-service-db-2) export DB2_WAL_PATH=$(oc exec -n ${NAMESPACE} deploy/rook-ceph-tools -- \ ceph fs subvolume getpath ${FS_NAME} common-service-db-2-wal) export STATIC_SECRET=cephfs-static-secret # This custom secret was created for static volumes. export NAMESPACE="rook-ceph" export FS_NAME="myfs" export CLUSTER_ID="rook-ceph" - Create the following PV:
apiVersion: v1 kind: PersistentVolume metadata: name: common-service-db-1 spec: capacity: storage: 20Gi accessModes: - ReadWriteOnce persistentVolumeReclaimPolicy: Retain csi: driver: rook-ceph.cephfs.csi.ceph.com nodeStageSecretRef: name: ${STATIC_SECRET} namespace: ${NAMESPACE} volumeAttributes: clusterID: ${CLUSTER_ID} fsName: ${FS_NAME} staticVolume: "true" rootPath: ${DB1_PATH} volumeHandle: common-service-db-1-handle --- apiVersion: v1 kind: PersistentVolume metadata: name: common-service-db-1-wal spec: capacity: storage: 10Gi accessModes: - ReadWriteOnce persistentVolumeReclaimPolicy: Retain csi: driver: rook-ceph.cephfs.csi.ceph.com nodeStageSecretRef: name: ${STATIC_SECRET} namespace: ${NAMESPACE} volumeAttributes: clusterID: ${CLUSTER_ID} fsName: ${FS_NAME} staticVolume: "true" rootPath: ${DB1_WAL_PATH} volumeHandle: common-service-db-1-wal-handle --- apiVersion: v1 kind: PersistentVolume metadata: name: common-service-db-2 spec: capacity: storage: 20Gi accessModes: - ReadWriteOnce persistentVolumeReclaimPolicy: Retain csi: driver: rook-ceph.cephfs.csi.ceph.com nodeStageSecretRef: name: ${STATIC_SECRET} namespace: ${NAMESPACE} volumeAttributes: clusterID: ${CLUSTER_ID} fsName: ${FS_NAME} staticVolume: "true" rootPath: ${DB2_PATH} volumeHandle: common-service-db-2-handle --- apiVersion: v1 kind: PersistentVolume metadata: name: common-service-db-2-wal spec: capacity: storage: 10Gi accessModes: - ReadWriteOnce persistentVolumeReclaimPolicy: Retain csi: driver: rook-ceph.cephfs.csi.ceph.com nodeStageSecretRef: name: ${STATIC_SECRET} namespace: ${NAMESPACE} volumeAttributes: clusterID: ${CLUSTER_ID} fsName: ${FS_NAME} staticVolume: "true" rootPath: ${DB2_WAL_PATH} volumeHandle: common-service-db-2-wal-handle - Verify the PVs with the
oc get pvcommand.The output resembles the following example:
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE common-service-db-1 20Gi RWO Retain Available 1m common-service-db-1-wal 10Gi RWO Retain Available 1m common-service-db-2 20Gi RWO Retain Available 1m common-service-db-2-wal 10Gi RWO Retain Available 1m
- Create a YAML file with all PVs. Four PVs are defined to support two
- Create PVCs.
- Create a YAML file with PVCs that bind to your static PVs.
- The
accessModesvalues must match the PV access modes. - The PVC
storagerequest must either match or be less than the PV capacity. - The
volumeNamemust match the PV name. - The
storageClassName: ""empty string is required for static PV binding.
apiVersion: v1 kind: PersistentVolumeClaim metadata: name: common-service-db-1 namespace: ibm-common-services # Change this value to your target namespace. spec: accessModes: - ReadWriteOnce resources: requests: storage: 20Gi volumeName: common-service-db-1 storageClassName: "" # The empty string is required for static binding. --- apiVersion: v1 kind: PersistentVolumeClaim metadata: name: common-service-db-1-wal namespace: ibm-common-services spec: accessModes: - ReadWriteOnce resources: requests: storage: 10Gi volumeName: common-service-db-1-wal storageClassName: "" --- apiVersion: v1 kind: PersistentVolumeClaim metadata: name: common-service-db-2 namespace: ibm-common-services spec: accessModes: - ReadWriteOnce resources: requests: storage: 20Gi volumeName: common-service-db-2 storageClassName: "" --- apiVersion: v1 kind: PersistentVolumeClaim metadata: name: common-service-db-2-wal namespace: ibm-common-services spec: accessModes: - ReadWriteOnce resources: requests: storage: 10Gi volumeName: common-service-db-2-wal storageClassName: "" - The
- Verify the PVC binding. First, check the PVC status:
oc get pvc -n ibm-common-servicesIn the output, all PVCs have theBoundstatus:# NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE # common-service-db-1 Bound common-service-db-1 20Gi RWO 10s # common-service-db-1-wal Bound common-service-db-1-wal 10Gi RWO 10s # common-service-db-2 Bound common-service-db-2 20Gi RWO 10s # common-service-db-2-wal Bound common-service-db-2-wal 10Gi RWO 10s - Verify that the PV is bound to the PVC:
oc get pv | grep common-service-db
- Create a YAML file with PVCs that bind to your static PVs.
- Install foundational services and wait for the foundational services pods to run.
- Install ZenService. A storage class must be available to install ZenService. Then, wait for the ZenService pods to run.