Storage
Persistent volumes, NFS mounts, S3 connections, and storage management
Overview
PodWarden supports persistent storage for workloads that need data to survive pod restarts — databases, media servers, ML models, object storage, and more. Storage is configured through volume mounts on stacks and optional storage connections for shared backends.
Developer reference: see compose-rwx-access-modes for the compose-bridge translator details.
Volume Types
Each volume mount has a type that determines how storage is provisioned:
| Type | Persistence | Use Case |
|---|---|---|
emptyDir | Ephemeral (lost on restart) | Scratch space, temp files, caches |
hostPath | Tied to specific host | Development, single-node data |
configMap | Read-only config | Configuration files (see also Config Files for managed config slots) |
secret | Read-only secrets | TLS certs, credentials |
pvc | Persistent (survives restarts) | Databases, application data |
nfs | Network-attached | Shared data across workloads |
s3 | Object storage (env injection) | Media, backups, ML datasets |
emptyDir
Temporary storage that exists for the lifetime of the pod. Fast and simple, but data is lost when the pod restarts.
When to use: Temp files, build caches, inter-container shared directories.
hostPath
Mounts a directory from the host machine directly into the container. Data persists on that specific host but is not portable across servers.
When to use: Development environments, single-node deployments where simplicity matters.
Fields: host_path (directory on host), host_path_type (DirectoryOrCreate, Directory, File, etc.)
PVC (Persistent Volume Claim)
Block storage provisioned by a StorageClass. PVCs survive pod restarts and redeployments. This is the recommended approach for stateful workloads.
When to use: Databases (PostgreSQL, MySQL, Redis), application data that must persist.
Fields:
| Field | Description |
|---|---|
size | Storage size (e.g. 10Gi, 100Gi) |
access_mode | ReadWriteOnce, ReadOnlyMany, or ReadWriteMany — see Access Modes below |
storage_class | StorageClass name (optional, uses cluster default) |
retain_on_undeploy | Keep PVC data when workload is undeployed (default: true) |
StorageClass requirement: Your cluster needs at least one StorageClass provisioner (e.g. Longhorn, OpenEBS, local-path). Check your cluster's StorageClasses in the cluster detail page — PodWarden shows available classes and warns when none exist.
PVC lifecycle:
- On deploy: PodWarden creates a PVC named
{deployment}-{volume-name}before applying the deployment - On undeploy with
retain_on_undeploy: true: PVC and data are kept (default) - On undeploy with
retain_on_undeploy: false: PVC is deleted along with the deployment
Once provisioned, a PVC's access mode is fixed for its lifetime; switching requires recreation. Re-importing a compose stack does NOT change an already-provisioned PVC's access mode — recreate the PVC explicitly if you want to switch from RWO to RWX or back.
Access Modes
Kubernetes PVCs use an access mode to declare how many nodes may attach the volume at once:
| Mode | Short | Meaning |
|---|---|---|
ReadWriteOnce | RWO | One node may mount the volume read/write at a time |
ReadWriteMany | RWX | Any number of nodes may mount the volume read/write simultaneously |
ReadOnlyMany | ROX | Any number of nodes may mount the volume read-only simultaneously |
When to use each mode:
- ReadWriteOnce (default): Use for every single-service workload — databases (PostgreSQL, MySQL, Redis), application state, media servers. The vast majority of workloads need this. PodWarden defaults all new PVC mounts to RWO.
- ReadWriteMany: Use when two or more services (pods) in the same compose stack need to share a volume with concurrent writes — for example, a web service and a background worker both writing to an uploads directory. RWX requires your cluster to have an RWX-capable StorageClass (a StorageClass whose underlying provisioner can produce volumes mountable read/write by multiple Pods, e.g., Longhorn's
longhornclass with share-manager enabled, or any NFS-backed class) — see RWX in PodWarden below. - ReadOnlyMany: Use when multiple services need to read a pre-seeded dataset or configuration directory without writing. Rare — most use cases are better served by ConfigMaps or S3.
Never put a PostgreSQL, MySQL, or Redis volume on ReadWriteMany. These databases use file-level locking that breaks when the volume is attached from multiple nodes. Keep database volumes on ReadWriteOnce even if the rest of your compose stack is shared.
Compose stack auto-promotion
When you import a compose file, PodWarden inspects every named volume and decides its access mode automatically:
- Explicit override wins. If your compose file contains an
x-podwarden.volumes.<name>.access_modesannotation, PodWarden uses it exactly — no inference. - Auto-promote to RWX. If the same named volume is mounted by two or more distinct services with at least one read/write mount, PodWarden sets
ReadWriteManyon the resulting PVC. This fires automatically without any annotation. - Default to RWO. All other volumes stay
ReadWriteOnce.
The auto-promotion rule exists so that multi-service compose stacks (like PageWarden, where site-data is shared between the node runtime and the file-sidecar) work out of the box. PodWarden shows a notice in the compose import wizard for every volume that was auto-promoted, so you can review the decision before deploying.
Example — explicit x-podwarden annotation:
x-podwarden:
volumes:
site-data:
access_modes: [ReadWriteMany] # shared between node + file-sidecar
pg-data:
access_modes: [ReadWriteOnce] # postgres only — keep RWO
services:
node:
volumes:
- site-data:/site
file-sidecar:
volumes:
- site-data:/site # same volume → auto-promoted to RWX without annotation
postgres:
volumes:
- pg-data:/var/lib/postgresql/data # single service → stays RWO
volumes:
site-data:
pg-data:Important: auto-promotion only fires for compose stack imports. Volumes added manually via the volume-mounts editor always start at ReadWriteOnce — set the access mode explicitly in the editor.
RWX in PodWarden
PodWarden supports two backing paths for ReadWriteMany PVCs. The volume-mounts editor shows which path applies with an RWO/RWX badge and, when a matching storage connection exists, a hint linking the volume to its NFS share name.
Path 1 — Longhorn share-manager (native, recommended)
If your cluster runs Longhorn v1.6 or later (PodWarden installs Longhorn v1.6 or later — chart 1.7.3 today — on all new clusters), Longhorn handles RWX natively without any external NFS server. When you create an RWX PVC, Longhorn spawns a share-manager-* pod in the longhorn-system namespace. Longhorn's share-manager pod exports the volume to participating workloads (implementation detail; treat the share-manager pod as opaque).
What this looks like in the cluster:
# RWX PVC shows ACCESSMODES: RWX and is bound
kubectl get pvc -A | grep RWX
# Longhorn creates one share-manager pod per RWX volume
kubectl -n longhorn-system get pods -l longhorn.io/component=share-manager
# share-manager-pvc-bb92a7be-1f54-4a3f-a8d2-... 1/1 RunningThe pod name uses the underlying PV's UUID, not the PVC name — find it with kubectl -n longhorn-system get pods -l longhorn.io/component=share-manager.
No configuration is required. If the cluster default StorageClass is longhorn (true for all PodWarden-provisioned clusters as of 2026-05-12), PodWarden simply sets accessModes: [ReadWriteMany] on the PVC and Longhorn handles the rest.
Considerations:
- Each RWX volume consumes one extra
share-manager-*pod inlonghorn-system. - The share-manager adds a small network hop vs direct block attach. For write-heavy workloads (databases) use RWO instead.
- Share-manager pods are reschedulable — if the node hosting the pod goes down, Longhorn migrates the share-manager pod to another node. There is a brief reconnect window (~10s) during migration.
Path 2 — NFS-subdir via storage connection
If you have an existing NFS server (a NAS, a dedicated storage VM, or a self-hosted NFS export), you can register it as a storage connection in PodWarden. PodWarden deploys an nfs-subdir-external-provisioner to the cluster, creating a StorageClass that auto-provisions per-volume subdirectories on your NFS export.
This path is described fully in NFS StorageClass Provisioning below.
Decision guide:
| Situation | Recommended path |
|---|---|
| New PodWarden cluster (Longhorn installed) | Path 1 — Longhorn share-manager |
| Existing NFS server already available | Path 2 — NFS storage connection |
| Air-gapped cluster, no external NFS | Path 1 — Longhorn share-manager |
| Need to share a volume with workloads outside Kubernetes | Path 2 — NFS (the export is reachable externally) |
Use Longhorn share-manager unless you already have an NFS server. Both paths produce ReadWriteMany PVCs and are equally supported by PodWarden.
NFS
Mounts a remote NFS share directly into the container. Useful for shared storage accessible from multiple workloads or servers.
When to use: Shared media libraries, datasets accessed by multiple workloads, existing NFS infrastructure.
Two ways to configure:
- Direct: Specify
server(IP or hostname) andpathdirectly on the volume mount - Via storage connection: Reference a named NFS storage connection — PodWarden resolves the server and path at deploy time
Fields: server, path, read_only, or storage_connection (name of a registered NFS connection)
S3 (Credential Injection)
References an S3 storage connection and injects its credentials as environment variables into the container. The workload application uses these env vars to connect to S3 directly.
When to use: Object storage for media files, backups, ML model artifacts.
Injected environment variables:
| Variable | Source |
|---|---|
S3_ENDPOINT | Connection config |
S3_BUCKET | Connection config |
S3_REGION | Connection config |
S3_ACCESS_KEY | App secret {connection_name}_access_key |
S3_SECRET_KEY | App secret {connection_name}_secret_key |
Storage Connections
Storage connections are global named backends that you register once and reference from any workload. Navigate to Storage in the sidebar to manage them.
NFS Connection
Register an NFS server so workloads can reference it by name instead of hardcoding IP addresses.
Config fields:
- Server — NFS server hostname or IP address
- Base path — Export path on the server (e.g.
/exports/data)
Testing: The test button runs connectivity checks:
- TCP port 2049 reachability
- RPC portmapper response
- NFS export listing
- If managed hosts are available: SSH-based mount + 100 MB read/write speed test
S3 Connection
Register an S3-compatible endpoint (AWS S3, MinIO, SeaweedFS, RustFS, etc.) with credentials stored securely in PodWarden's encrypted secrets.
Config fields:
- Endpoint — S3 endpoint URL (e.g.
https://s3.amazonaws.com) - Bucket — Bucket name
- Region — AWS region or empty for non-AWS
- Access Key — Stored as encrypted app secret
- Secret Key — Stored as encrypted app secret
Testing: The test button verifies:
- Endpoint reachability
- Authentication (SigV4-signed request)
- Upload + download speed test (100 MB)
NFS StorageClass Provisioning
PodWarden can deploy an NFS provisioner to a cluster, creating a StorageClass that auto-provisions PVCs on your NFS server. This is useful when you want workloads to use NFS-backed persistent volumes without manually configuring each volume mount as type nfs.
Via MCP:
create_nfs_storage_class(
connection_id="...", # UUID of an NFS storage connection
cluster_id="...", # target cluster
storage_class_name="nfs", # name for the StorageClass
reclaim_policy="Retain" # "Retain" or "Delete"
)What this deploys:
- A
nfs-provisionernamespace - RBAC (ServiceAccount, ClusterRole, ClusterRoleBinding, Role, RoleBinding)
- An
nfs-subdir-external-provisionerdeployment that watches for PVC requests - A StorageClass named
nfs(or your chosen name)
Once created, any workload can use storage_class: "nfs" in its volume mounts, and the provisioner will automatically create subdirectories on the NFS server for each PVC.
NFS StorageClasses only work on clusters where nodes can reach the NFS server. For mixed LAN/mesh clusters, this typically means only nodes on the same LAN (e.g. 10.10.x.x network) can use NFS volumes.
Adding Volume Mounts to a Workload
- Go to Apps & Stacks and create or edit a definition
- Scroll to the Volume Mounts section
- Click Add Volume Mount
- Select the volume type and fill in the fields:
- Name — Unique name for this volume (used in PVC naming)
- Mount Path — Path inside the container (e.g.
/data,/var/lib/postgresql/data) - Read Only — Mount as read-only (optional)
- Type-specific fields — Size, StorageClass, NFS server, etc.
- Save the stack
When you deploy the workload, PodWarden automatically:
- Creates PVCs before applying the deployment
- Resolves storage connection references to actual server addresses
- Injects S3 credentials as environment variables
- Generates the correct Kubernetes volume and volumeMount specs
StorageClass Discovery
Each cluster's detail page shows available StorageClasses — the provisioners that create persistent volumes on demand. If your cluster has no StorageClasses, or the cluster API is temporarily unreachable, PodWarden indicates this in the StorageClasses section.
Common StorageClass provisioners:
- Longhorn — Distributed block storage, replication, snapshots
- OpenEBS — Container-attached storage with multiple engines
- local-path — Simple local storage (ships with K3s by default)
Examples
PostgreSQL with PVC
Volume Mount:
Name: pgdata
Mount Path: /var/lib/postgresql/data
Type: PVC
Size: 50Gi
Access Mode: ReadWriteOnce
Retain on Undeploy: trueJellyfin with NFS Media Library
Volume Mount:
Name: media
Mount Path: /media
Type: NFS (via storage connection)
Storage Connection: nas-media
Read Only: trueML Training with S3 Dataset
Volume Mount:
Name: s3-datasets
Type: S3
Storage Connection: training-bucket
Container receives:
S3_ENDPOINT=https://s3.us-east-1.amazonaws.com
S3_BUCKET=ml-datasets
S3_REGION=us-east-1
S3_ACCESS_KEY=AKIA...
S3_SECRET_KEY=wJal...