PodWarden
Guides

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:

TypePersistenceUse Case
emptyDirEphemeral (lost on restart)Scratch space, temp files, caches
hostPathTied to specific hostDevelopment, single-node data
configMapRead-only configConfiguration files (see also Config Files for managed config slots)
secretRead-only secretsTLS certs, credentials
pvcPersistent (survives restarts)Databases, application data
nfsNetwork-attachedShared data across workloads
s3Object 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:

FieldDescription
sizeStorage size (e.g. 10Gi, 100Gi)
access_modeReadWriteOnce, ReadOnlyMany, or ReadWriteMany — see Access Modes below
storage_classStorageClass name (optional, uses cluster default)
retain_on_undeployKeep 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:

  1. On deploy: PodWarden creates a PVC named {deployment}-{volume-name} before applying the deployment
  2. On undeploy with retain_on_undeploy: true: PVC and data are kept (default)
  3. 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:

ModeShortMeaning
ReadWriteOnceRWOOne node may mount the volume read/write at a time
ReadWriteManyRWXAny number of nodes may mount the volume read/write simultaneously
ReadOnlyManyROXAny 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 longhorn class 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:

  1. Explicit override wins. If your compose file contains an x-podwarden.volumes.<name>.access_modes annotation, PodWarden uses it exactly — no inference.
  2. 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 ReadWriteMany on the resulting PVC. This fires automatically without any annotation.
  3. 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   Running

The 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 in longhorn-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:

SituationRecommended path
New PodWarden cluster (Longhorn installed)Path 1 — Longhorn share-manager
Existing NFS server already availablePath 2 — NFS storage connection
Air-gapped cluster, no external NFSPath 1 — Longhorn share-manager
Need to share a volume with workloads outside KubernetesPath 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:

  1. Direct: Specify server (IP or hostname) and path directly on the volume mount
  2. 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:

VariableSource
S3_ENDPOINTConnection config
S3_BUCKETConnection config
S3_REGIONConnection config
S3_ACCESS_KEYApp secret {connection_name}_access_key
S3_SECRET_KEYApp 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:

  1. TCP port 2049 reachability
  2. RPC portmapper response
  3. NFS export listing
  4. 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:

  1. Endpoint reachability
  2. Authentication (SigV4-signed request)
  3. 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:

  1. A nfs-provisioner namespace
  2. RBAC (ServiceAccount, ClusterRole, ClusterRoleBinding, Role, RoleBinding)
  3. An nfs-subdir-external-provisioner deployment that watches for PVC requests
  4. 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

  1. Go to Apps & Stacks and create or edit a definition
  2. Scroll to the Volume Mounts section
  3. Click Add Volume Mount
  4. 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.
  5. 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: true

Jellyfin with NFS Media Library

Volume Mount:
  Name: media
  Mount Path: /media
  Type: NFS (via storage connection)
  Storage Connection: nas-media
  Read Only: true

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