Skip to content

Storage resources

This page is the schema reference for Rune’s storage resources. For the big-picture model and the lifecycle, see the storage concept.

Cluster-scoped. Names a driver and default parameters.

storageClass:
name: do-nyc3
driver: do-volume
parameters:
region: nyc3
fsType: ext4
apiTokenSecretRef: do/api-token
reclaimPolicy: retain
default: false
allowedTopologies:
- matchLabels:
rune.io/region: nyc3
- matchExpressions:
- key: rune.io/zone
operator: In
values: [nyc3a, nyc3b]
labels:
tier: cloud
FieldTypeRequiredNotes
namestringyesDNS-1123. Cluster-unique.
driverstringyesRegistered driver name (e.g. local, local-host, do-volume).
parametersmap[string]stringnoDriver-specific. See driver tables below.
reclaimPolicyenumnoretain (default) or delete. Per-volume override allowed.
defaultboolnoAt most one class may be true. API server enforces uniqueness.
allowedTopologies[]TopologySelectornoOptional placement constraints; matched against node labels.
labelsmap[string]stringnoFree-form labels.

TopologySelector is a list of matchLabels and/or matchExpressions (operators: In, NotIn, Exists, DoesNotExist).

Any value in a StorageClass or Volume parameters map may be a secret: reference instead of a literal. The controller resolves the reference against the secrets store before the driver sees it, so drivers always receive plaintext.

storageClass:
name: do-nyc3
driver: do-volume
parameters:
region: nyc3
# FQDN: pinned to a specific namespace — recommended for
# cluster-scoped StorageClasses since one shared secret serves
# every namespace's volumes.
apiToken: secret:do-api-token.shared.rune/token

Form: secret:<name>[.<namespace>.rune]/<key>. Values without the secret: prefix pass through verbatim. A missing secret or key fails the operation with InvalidParameters.

Shorthand vs FQDN — important for StorageClass

Section titled “Shorthand vs FQDN — important for StorageClass”

The shorthand form secret:<name>/<key> (no namespace) is resolved against the consuming Volume’s namespace, not the StorageClass’s. Since StorageClass is cluster-scoped, a single class with a shorthand ref will look up the secret in whichever namespace happens to be using the class at that moment — so every namespace would need its own copy of the secret. For shared infrastructure tokens (DO API token, S3 credentials, etc.) use the FQDN form to pin the lookup to one namespace regardless of which Volume triggered the operation. Shorthand is fine on Volume.parameters overrides (the Volume already has its own namespace).

Namespaced. A unit of durable storage.

volume:
name: pgdata-postgres-0
namespace: prod
storageClassName: local
size: 10Gi
accessMode: ReadWriteOnce
reclaimPolicy: retain
parameters: {}
labels:
app: postgres
FieldTypeRequiredNotes
namestringyesDNS-1123. Unique within the namespace.
namespacestringnoDefault default.
storageClassNamestringconditionalRequired unless a default class is set. Falls back to runefile.[storage].defaultStorageClass.
sizequantityyesE.g. 10Gi, 500Mi, 1G. Kubernetes Quantity syntax: binary suffixes (Ki/Mi/Gi/…) are powers of two, SI suffixes (K/M/G/…) are decimal. Unitless integers are bytes, not gigabytes — write 10Gi, not 10. Driver may treat as informational (e.g. hostPath).
accessModeenumyesReadWriteOnce, ReadOnlyMany, ReadWriteMany. Driver-gated.
reclaimPolicyenumnoretain or delete. Defaults to the class’s policy.
parametersmap[string]stringnoPer-volume overrides merged on top of class parameters.
labelsmap[string]stringnoFree-form.
KeyNotes
(none required)Rune manages the directory under runefile.[storage].localVolumeRoot.
KeyNotes
hostPathRequired. Absolute path; must sit under runefile.[storage].hostPathAllowlist.
createIfMissing"true" to create the directory if missing. Honoured only when [storage] allowCreateMissing = true.
KeyNotes
regionRequired. DigitalOcean region (e.g. nyc3). Block-storage volumes are region-pinned — see Region pinning below.
fsTypeext4 (default), xfs.
apiTokenDO API token. Accepts a literal value or a secret reference like secret:do-api-token/token.
apiTokenSecretRefLegacy. <namespace>/<secret-name> form, kept for runefile back-compat. Prefer the generic secret: reference on apiToken above.

A Full Access token works, but the driver only needs the following custom scopes. These mirror exactly the eight HTTP endpoints the driver calls (pkg/storage/driver/dovolume/client.go); anything else is over-privileged.

ResourceOperationsDO endpoint(s) the driver hitsWhat fails without it
block_storagecreate, read, deletePOST/GET/DELETE /v2/volumes[/{id}]Provision (create), reconcile/observe (read), reclaim (delete).
block_storage_actioncreatePOST /v2/volumes/{id}/actionsAttach, detach, and online-resize. This is the one most operators miss — provisioning succeeds and then attach silently 401s, leaving the volume stuck Available with the instance pending.
actionsreadGET /v2/actions/{id}Polling the async action to completion. Without it, attach/detach return an action ID the driver can’t observe and times out as Stalled.
dropletreadGET /v2/droplets?name=<node>Translating the Rune node ID into a DO droplet ID for the attach call.
block_storage_snapshotcreate, read, deletePOST /v2/volumes/{id}/snapshots, DELETE /v2/snapshots/{id}Only required if you use rune snapshot create / restore against volumes on this class. Omit if you don’t use snapshots.

The driver does not call /v2/regions, /v2/sizes, or update the volume metadata, so regions:read, sizes:read, and block_storage:update are not needed despite what the DO console’s default scope hints sometimes suggest.

DO block-storage volumes belong to a single region. The driver provisions in the region named on the StorageClass, and DO refuses to attach a volume to a droplet in a different region. For a multi-region cluster, create one StorageClass per region:

storageClass:
name: do-volumes-nyc3
driver: do-volume
parameters: { region: nyc3, fsType: ext4, apiTokenSecretRef: shared/do-api-token }
---
storageClass:
name: do-volumes-fra1
driver: do-volume
parameters: { region: fra1, fsType: ext4, apiTokenSecretRef: shared/do-api-token }

Use allowedTopologies (RUNE-072) or the namespace scoping of your StorageClass references to keep claims targeted at the right region.

FieldNotes
statusPending, Provisioning, Available, Bound, Released, Failed, Stalled.
handleDriver-specific identifier (path, volume ID, …).
boundClaimThe instance/claim currently bound to this volume.
boundNodeNode where the volume is currently attached.
ownerServiceSet when the volume was created from a service claimTemplate.
failureMessageLast driver/controller error if Failed/Stalled.
attemptsProvision retry count.
driverParametersController-captured snapshot of the merged StorageClass + Volume parameters (post-secret-resolution-source) at successful Provision time. Used by reclaim Delete / Detach / Unmount when the class has been deleted before its volumes, so orphan cleanup still has the parameters the driver needs. Read-only.

Namespaced. Point-in-time copy of one volume.

snapshot:
name: pgdata-2025-11-15
namespace: prod
source:
volume: pgdata-postgres-0
labels:
app: postgres
FieldTypeRequiredNotes
namestringyesDNS-1123. Unique within the namespace.
namespacestringnoDefault default.
source.volumestringyesSource volume name in the same namespace.
labelsmapnoFree-form.

The source volume’s driver must advertise Capabilities.Snapshots = true — the API server rejects writes against drivers that don’t (e.g. local-host).

FieldNotes
statusPending, Creating, Ready, Deleting, Failed.
handleDriver-specific snapshot identifier.
failureMessageLast driver/controller error if Failed.

Top-level field on the service spec. See also the service spec reference.

service:
name: postgres
scale: 3
volumes:
- name: pgdata
mountPath: /var/lib/postgresql/data
readOnly: false
subPath: ""
claimTemplate:
storageClassName: local
size: 10Gi
accessMode: ReadWriteOnce
FieldTypeRequiredNotes
namestringyesMount identifier. Unique within the service.
mountPathstringyesAbsolute path inside the container/process. Blocklist: /, /etc, /proc, /sys, /var/run/docker.sock.
readOnlyboolnoMount read-only. Default false.
subPathstringnoMount a sub-directory of the volume.
claimobjectconditionalExactly one of claim or claimTemplate.
claimTemplateobjectconditionalExactly one of claim or claimTemplate.
claim:
name: web-data # bare name → same namespace as the service
# name: shared.common.rune # FQDN form → cross-namespace

Cast-time error: an RWO claim mount on a service with scale > 1. Use claimTemplate for stateful sets.

claimTemplate:
storageClassName: local # optional; defaults to the cluster default
size: 10Gi # required
accessMode: ReadWriteOnce # required
parameters: {} # optional driver-specific overrides
reclaimPolicy: retain # optional override

Per-replica volumes are auto-provisioned with stable names of the form <volume-name>-<service-name>-<ordinal>, e.g. pgdata-postgres-0.

All of the following are checked at cast time and on every API write:

  • Exactly one of claim / claimTemplate per mount entry.
  • mountPath is absolute, unique within the service, doesn’t overlap any secretMounts/configMounts path, and isn’t in the blocklist.
  • For RWO claim mounts: service.scale == 1.
  • claimTemplate.accessMode is in the chosen driver’s Capabilities.AccessModes.
  • Snapshot writes against drivers without Capabilities.Snapshots are rejected.
  • local-host hostPath is absolute, has no .., sits under runefile.[storage].hostPathAllowlist.
  • Process-runtime services may use local-host only; block-device drivers (do-volume) are rejected at cast time.
  • A Volume whose reclaimPolicy: delete targets the local-host driver is rejected.