Shared storage (RWX)
The cluster's default StorageClasses (csi-cinder-*) are all OVH Cinder block volumes — ReadWriteOnce only, so they can't be mounted by multiple pods. For workloads that need to share files between pods (e.g. uploads, generated assets, shared caches) the cluster runs an in-cluster NFS server that exposes a single ReadWriteMany StorageClass: nfs.
How it works
┌──────────────────────────────────────────────────────┐
│ namespace: nfs-server-provisioner │
│ │
│ StatefulSet nfs-server-provisioner-0 │
│ │ │
│ │ writes to │
│ ▼ │
│ PVC (csi-cinder-high-speed-gen2, 20Gi, RWO) ◀──┐ │
│ │ │
└──────────────────────────────────────────────────┼──┘
│
StorageClass `nfs` ──────────────────────────────► │
provisions one subdirectory per PVC inside that volume
- One Cinder block volume backs the entire shared pool.
- The NFS server pod exposes it as NFSv4.1.
- Every app PVC with
storageClassName: nfsbecomes a subdirectory under that volume, mounted RWX by every pod that claims it.
Where it lives
- ArgoCD app:
k8s/app/system/nfs-server-provisioner/nfs-server-provisioner.app.yaml - Helm values:
k8s/app/system/nfs-server-provisioner/helm/values.yaml - Chart:
nfs-server-provisioner(kubernetes-sigs)
Using it from an app chart
Every app chart pulls in the component-shared-volume subchart. It's driven by a single list under global — empty by default, so nothing is rendered. Add entries in your project values file:
global:
sharedVolumes:
- name: shared
size: 2Gi
mounts:
- path: /var/app/var/cache
subPath: cache
- path: /var/app/public/cache
subPath: public-cache
- name: uploads
size: 20Gi
mounts:
- path: /var/app/public/uploads
This renders one PVC per entry (<nameOverride>-shared, <nameOverride>-uploads) on the nfs StorageClass, plus matching volumes: and volumeMounts: on the workload — including multiple subPath mounts of the same PVC where listed. All replicas see the same directories.
Practical guidance: /uploads, /cache, /public/cache
Two common shapes:
- One PVC, three mounts — when the three paths belong to one app and grow together (typical Symfony). Mount
cache/,public-cache/, anduploads/subdirectories of a single PVC. Cheaper and simpler; one capacity number to tune. - Two PVCs — when one path is user-precious (uploads) and the others are rebuildable cache. Splitting lets you size/retain them differently and makes it safe to
kubectl delete pvcthe cache if it ever gets weird.
See component-shared-volume for a side-by-side comparison.
When to use it
Good fit:
- User uploads / generated images that any replica may serve
- Shared cache directories (e.g. Symfony
var/cachewhen warm-up is expensive) - Cross-pod file handoffs between web and worker pods
Not a good fit:
- Database storage — use Cinder block (
csi-cinder-high-speed-gen2) via the database operator - High-throughput / latency-sensitive paths — NFS adds round-trips
- Anything that must survive an NFS-server pod restart with zero blip — there's only one server pod
Operational notes
- Single point of failure.
nfs-server-provisioneris a single pod; a restart causes a short NFS reconnect for every consumer. - Pool capacity. All RWX PVCs live inside the one 20 GiB backing Cinder volume. Raise
persistence.sizein the chart values if you outgrow it (the PVC hasallowVolumeExpansion: true). - Reclaim policy. The
nfsStorageClass usesDelete— removing a project's PVC removes its data. Switch toRetainin the values file if a consumer's data is precious.