> ## Documentation Index
> Fetch the complete documentation index at: https://docs.nscale.com/llms.txt
> Use this file to discover all available pages before exploring further.

# File storage on NKS

> Give pods on an Nscale Kubernetes Service (NKS) cluster shared, persistent, POSIX filesystem storage with Nscale file storage and PersistentVolumeClaims.

This guide is for teams running workloads on an [Nscale Kubernetes Service (NKS)](/docs/platform-services/managed-kubernetes) cluster who want shared, persistent, POSIX filesystem storage for their pods. The storage is Nscale [file storage](/docs/storage/file-storage), shown as **Filesystem** in the console.

The Nscale File Storage CSI Driver and a high-performance NFS client on the nodes are already installed and maintained for you, so there is nothing to deploy. You create one Kubernetes `StorageClass`, and from then on your teams request storage with ordinary `PersistentVolumeClaim` objects.

<Note>
  **Before you start, you need:**

  * `kubectl` access to the cluster. See [Access your cluster](/docs/platform-services/managed-kubernetes#access-your-cluster), or run `nscale kubernetes kubeconfig get <cluster-id> --merge`.
  * File storage available to you. It's currently offered only in the reserved cloud environment, and the cluster's VPC needs at least a **/24** CIDR block. See [Filesystem availability](/docs/storage/file-storage#availability).
  * The [Nscale CLI](/docs/cli/overview), logged in with `nscale login`, to look up the file storage class ID. The console doesn't show class IDs.
</Note>

## What is already in your cluster

| Component | Who provides it | What it does |
| - | - | - |
| Nscale File Storage CSI Driver (`filestorage.csi.nscale.com`) | NKS | Creates, expands, and deletes file storage for your PVCs, and mounts it in your pods |
| NFS client on every node | NKS | Provides the high-performance NFS client used by the optional [attachment mount options](#attachment-mount-options-and-performance) |
| `StorageClass` | **You** | Chooses which kind of file storage to create, and what happens when a PVC is deleted |

## Step 1: Find your file storage class ID

A **file storage class** is an Nscale-side definition of the kind of storage to create: its performance characteristics and the storage features available in a region. Your Kubernetes `StorageClass` selects one by ID.

A file storage class belongs to a region, and you must use one from the **region your NKS cluster was provisioned in**. Find that region first. The cluster listing shows it in the `Region` column, or you can read it from the cluster directly:

```bash theme={null}
nscale kubernetes cluster list
nscale kubernetes cluster get <cluster-id> -q '.status.regionId'
```

Then list the file storage classes available in that region with [`nscale filestorage classes`](/docs/cli/filestorage#classes):

```bash theme={null}
nscale filestorage classes --region <region-id>
```

Copy the ID of the class you want. To extract just the IDs, or to inspect the full payload:

```bash theme={null}
nscale filestorage classes --region <region-id> -q '.[].metadata.id'
nscale filestorage classes --region <region-id> --json
```

<Warning>
  A class from a different region can't be attached to your cluster's network, and PVCs using it never bind.
</Warning>

## Step 2: Create a StorageClass

```yaml storageclass.yaml theme={null}
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: nscale-filestorage
provisioner: filestorage.csi.nscale.com
parameters:
  filestorage.csi.nscale.com/storage-class-id: "<nscale-file-storage-class-id>"
  # Optional, recommended: use the NFS mount options Nscale publishes for the
  # network attachment. Requires the NFS client that NKS installs on its nodes.
  filestorage.csi.nscale.com/use-attachment-mount-options: "true"
reclaimPolicy: Retain
allowVolumeExpansion: true
volumeBindingMode: Immediate
```

Apply and verify it:

```bash theme={null}
kubectl apply -f storageclass.yaml
kubectl get storageclass nscale-filestorage
```

### Fields

| Field | Value | Why |
| - | - | - |
| `provisioner` | `filestorage.csi.nscale.com` | The driver name. Must match exactly. |
| `reclaimPolicy` | `Retain` | Keeps your data when a PVC is deleted. See [Delete a PVC](#delete-a-pvc-and-the-retain-reclaim-policy). |
| `allowVolumeExpansion` | `true` | Lets users grow a PVC later. Growing is the only supported resize. |
| `volumeBindingMode` | `Immediate` | File storage is reachable from every node on the cluster network, so there is no reason to delay binding until a pod is scheduled. |

### Parameters

| Parameter | Default | Meaning |
| - | - | - |
| `filestorage.csi.nscale.com/storage-class-id` | *required* | The file storage class ID from [step 1](#step-1-find-your-file-storage-class-id). |
| `filestorage.csi.nscale.com/use-attachment-mount-options` | `"false"` | When `"true"`, the NFS mount options Nscale publishes for the network attachment are applied at mount time. **Recommended.** |
| `filestorage.csi.nscale.com/root-squash` | `"true"` | When `"true"`, the export maps remote root to an unprivileged user. Set to `"false"` only if your workloads must write as root. |

Any other `filestorage.csi.nscale.com/*` parameter is rejected, and the PVC stays `Pending` with an `unsupported StorageClass parameter` error.

### Attachment mount options and performance

`use-attachment-mount-options: "true"` is optional, but recommended. When it is enabled, the driver passes through the mount options that Nscale publishes for the specific network attachment backing your volume, including the multipath options that let a single mount use several storage endpoints in parallel. Without it, the volume still mounts and works correctly, but it mounts with plain kernel NFS defaults and loses that extra throughput.

These options need the NFS client that NKS installs on its nodes, **so on NKS you can enable this safely.** On a self-managed cluster without it, the mount fails with `mount.nfs: an incorrect mount option was specified`.

### StorageClass mount options

You can add `mountOptions` to the `StorageClass`, and they are passed to every NFS mount for volumes created from it:

```yaml theme={null}
mountOptions:
  - nfsvers=4.1
```

A `StorageClass` mount option **overrides** an attachment mount option with the same key. If you have enabled `use-attachment-mount-options`, leave `mountOptions` empty unless you have a specific reason to override it. That way the options Nscale publishes for your attachment apply exactly as intended.

## Step 3: Create a PersistentVolumeClaim

```yaml pvc.yaml theme={null}
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: shared-data
  namespace: my-app
spec:
  accessModes:
    - ReadWriteMany
  storageClassName: nscale-filestorage
  resources:
    requests:
      storage: 100Gi
```

```bash theme={null}
kubectl -n my-app apply -f pvc.yaml
kubectl -n my-app get pvc shared-data -w
```

The PVC becomes `Bound` once the driver has created the file storage and Nscale has attached it to your cluster's network. This normally takes well under a minute.

File storage is sized in whole GiB, so a request is rounded **up** to the next GiB. Requesting `100Gi` gives you 100 GiB; requesting `500Mi` gives you 1 GiB.

Supported access modes:

| Access mode | Use it for |
| - | - |
| `ReadWriteMany` | The normal case. Many pods across many nodes read and write the same volume. |
| `ReadWriteOnce` | A single-writer filesystem volume. |

`ReadOnlyMany` and block volumes are not supported. Each PVC maps to exactly one file storage resource. To share data between pods, have them mount the same PVC with `ReadWriteMany`.

## Step 4: Mount the PVC in a pod

```yaml pod.yaml theme={null}
apiVersion: v1
kind: Pod
metadata:
  name: shared-data-writer
  namespace: my-app
spec:
  containers:
    - name: writer
      image: busybox:1.36
      command:
        - sh
        - -c
        - |
          echo "hello from ${HOSTNAME}" >> /data/hello.txt
          sleep 3600
      volumeMounts:
        - name: shared
          mountPath: /data
  volumes:
    - name: shared
      persistentVolumeClaim:
        claimName: shared-data
```

```bash theme={null}
kubectl -n my-app apply -f pod.yaml
kubectl -n my-app exec shared-data-writer -- sh -c 'df -h /data; cat /data/hello.txt'
```

Any number of pods, on any number of nodes, can mount the same `ReadWriteMany` PVC the same way. A `Deployment` or `StatefulSet` uses the identical `volumes` block.

## Grow a volume

If the `StorageClass` sets `allowVolumeExpansion: true`, edit the PVC request:

```bash theme={null}
kubectl -n my-app patch pvc shared-data \
  --type merge \
  -p '{"spec":{"resources":{"requests":{"storage":"200Gi"}}}}'

kubectl -n my-app get pvc shared-data -w
```

Expansion is **grow-only** and happens online: the mounted pods keep running and see the new size without a restart. Shrink requests are rejected.

## Delete a PVC and the `Retain` reclaim policy

The reclaim policy decides what happens to your data when someone deletes a PVC.

| | `Retain` (recommended) | `Delete` |
| - | - | - |
| PVC deleted | PV moves to `Released` | PV is removed |
| File storage | **Kept, with your data** | **Deleted, permanently** |
| Who removes the storage | You, deliberately | Kubernetes, immediately |

With `reclaimPolicy: Retain`, deleting the PVC never deletes your data. Kubernetes does not call the driver at all. The `PersistentVolume` is left behind in `Released` state and the file storage still exists in your project. From there you have three options:

1. **Delete it** with the Nscale CLI or in [console.nscale.com](https://console.nscale.com/), once you are sure.
2. **Re-use it.** Bind it to a new PVC in this cluster or in another cluster in the same project, or attach it to a compute instance. See [Use existing file storage](#use-existing-file-storage).
3. **Leave it.** It stays in the project and keeps using quota, so you still have to decide what to do with it later.

Find the file storage ID behind a released volume. The CSI `volumeHandle` **is** the file storage ID:

```bash theme={null}
kubectl get pv -o custom-columns='PV:.metadata.name,STATUS:.status.phase,CLAIM:.spec.claimRef.name,FILESTORAGE-ID:.spec.csi.volumeHandle'
```

Then remove the leftover Kubernetes object, and the storage itself if you no longer want it:

```bash theme={null}
kubectl delete pv <pv-name>

nscale filestorage get <file-storage-id>
nscale filestorage delete <file-storage-id>
```

Under `Retain`, deleting the `PersistentVolume` only removes the Kubernetes object. It does not touch the file storage; only the CLI, the console, or the Nscale API does that.

<Warning>
  **The reclaim policy is set on each PV when it is created.** Changing `reclaimPolicy` on the `StorageClass` affects only volumes provisioned after the change. To change an existing one:

  ```bash theme={null}
  kubectl patch pv <pv-name> \
    -p '{"spec":{"persistentVolumeReclaimPolicy":"Retain"}}'
  ```

  Check this on any PVC you care about before deleting it. If the PV says `Delete`, the data goes with it.
</Warning>

## Use existing file storage

Sometimes the storage exists before the PVC does: a volume retained from a deleted PVC, a dataset shared with another cluster or a compute instance, a filesystem seeded outside Kubernetes, or storage whose lifecycle a different team owns. In those cases you don't ask the driver to provision anything. You point Kubernetes at storage that already exists by writing the `PersistentVolume` yourself.

Such a volume is an **admin-managed volume**. The CSI driver only mounts it. It never creates, deletes, or resizes it, whatever happens to the Kubernetes objects.

<Warning>
  **The file storage must be attached to the network your NKS cluster uses.** The same file storage can be attached to more than one network, so a volume shared with another cluster or instance is normal. But nothing in this flow checks the attachment for you. If it is missing, the PVC still binds, but the pod hangs in `ContainerCreating` until the NFS mount times out.
</Warning>

### Collect the values

```bash theme={null}
nscale filestorage list --region <region-id> --project <project-id>
nscale filestorage get <file-storage-id> --json
```

From the output you need the file storage ID, and two values from the attachment for your cluster's network in `status.attachments[]`:

| Value | Where | Used as |
| - | - | - |
| `metadata.id` | The file storage itself | The `volumeHandle`, prefixed with `static:` |
| `mountSource` | The attachment, as `server:/export/path` | Split into the `server` and `export-path` volume attributes |
| `mountOptions` | The attachment, as a map such as `{"remoteports": "10.0.0.16-10.0.0.19"}` | One `key=value` entry per key in `spec.mountOptions`, for example `remoteports=10.0.0.16-10.0.0.19` |

Split `mountSource` on the **last** colon: for `10.0.0.16:/mnt/nfs` the server is `10.0.0.16` and the export path is `/mnt/nfs`. The same values are visible under **Storage → Filesystem** in the console.

### Write the PersistentVolume

```yaml pv.yaml theme={null}
apiVersion: v1
kind: PersistentVolume
metadata:
  name: shared-datasets
spec:
  capacity:
    storage: 200Gi                      # informational; at least the PVC request
  accessModes:
    - ReadWriteMany
  persistentVolumeReclaimPolicy: Retain
  storageClassName: ""                  # prevents a default StorageClass applying
  mountOptions:
    - remoteports=10.0.0.16-10.0.0.19   # from the attachment's mountOptions
  claimRef:                             # pre-bind, so only this PVC can claim it
    namespace: my-app
    name: shared-datasets
  csi:
    driver: filestorage.csi.nscale.com
    volumeHandle: static:<file-storage-id>
    volumeAttributes:
      filestorage.csi.nscale.com/server: "10.0.0.16"
      filestorage.csi.nscale.com/export-path: "/mnt/nfs"
```

Three details matter:

* **`volumeHandle: static:<file-storage-id>`.** The `static:` prefix is what protects your data. The driver recognizes it and refuses to delete or resize the file storage, so removing this `PersistentVolume` can never remove storage the driver did not create. Keeping the real ID after the prefix means `kubectl get pv` still shows which file storage backs the volume.
* **`persistentVolumeReclaimPolicy: Retain`.** With `Retain`, Kubernetes never asks the driver to delete anything in the first place. The `static:` handle is the backstop, not the primary control, so set both.
* **`claimRef`.** Without it, any PVC in any namespace that matches on capacity and access mode can bind this volume.

Put mount options in `spec.mountOptions`, not in a `filestorage.csi.nscale.com/mount-options` volume attribute. That attribute is split on commas and breaks a comma-separated value such as a `remoteports` list.

### Write the PersistentVolumeClaim

```yaml pvc.yaml theme={null}
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: shared-datasets
  namespace: my-app
spec:
  accessModes:
    - ReadWriteMany
  storageClassName: ""
  volumeName: shared-datasets
  resources:
    requests:
      storage: 200Gi
```

```bash theme={null}
kubectl apply -f pv.yaml
kubectl -n my-app apply -f pvc.yaml
kubectl -n my-app get pvc shared-datasets
```

`storageClassName: ""` on **both** objects keeps a default `StorageClass` from being applied, and the PVC can't request more than the PV's declared capacity. Pods then mount the PVC exactly as in [step 4](#step-4-mount-the-pvc-in-a-pod).

### What the driver does and does not do

| | PVC-provisioned volume | Existing file storage |
| - | - | - |
| Creates the file storage | Yes, from the PVC | No, you do |
| Attaches it to the cluster network | Yes | No, you do |
| Checks the network attachment | Yes, before binding | **No** |
| Mounts it on the node | Yes | Yes |
| Deletes it when the PV goes away | Only with `reclaimPolicy: Delete` | **Never** |
| Grows it when the PVC grows | Yes | **Never** (the resize is rejected) |

To resize storage used this way, grow the file storage through the CLI or console, then raise `spec.capacity` on the PV to match. Leave the PVC as it is: with `storageClassName: ""` there's no `StorageClass` that allows expansion, so the API server rejects a PVC resize.

## Troubleshooting

<AccordionGroup>
  <Accordion title="The PVC stays Pending with an unsupported StorageClass parameter error">
    The `StorageClass` has a `filestorage.csi.nscale.com/*` parameter the driver doesn't accept. Use only the [supported parameters](#parameters), then recreate the `StorageClass` and the PVC.
  </Accordion>

  <Accordion title="The PVC never binds">
    Check that `storage-class-id` is a class from the [cluster's region](#step-1-find-your-file-storage-class-id). A class from another region can't be attached to the cluster's network. Run `kubectl describe pvc <pvc-name>` to see the driver's events.
  </Accordion>

  <Accordion title="The pod hangs in ContainerCreating with existing file storage">
    The file storage isn't attached to the cluster's network, so the NFS mount times out. Attach it to that network in the console, or run [`nscale filestorage update <storage-id>`](/docs/cli/filestorage#update) without a file and add the network in the pre-filled prompts, then recreate the pod. A `--file` payload that only lists the network resets `posixAcl` and `atimeUpdateIntervalSeconds`. See [Use existing file storage](#use-existing-file-storage).
  </Accordion>

  <Accordion title="mount.nfs: an incorrect mount option was specified">
    The node doesn't have the NFS client that NKS installs, so it can't use the [attachment mount options](#attachment-mount-options-and-performance). On a self-managed cluster, set `use-attachment-mount-options` to `"false"`.
  </Accordion>

  <Accordion title="A PVC resize is rejected">
    Volumes can only grow. Check that the new request is larger than the current size, and that the `StorageClass` sets `allowVolumeExpansion: true`. For existing file storage, see [what the driver does and does not do](#what-the-driver-does-and-does-not-do).
  </Accordion>
</AccordionGroup>

***

## Related resources

<CardGroup cols={2}>
  <Card title="Managed Kubernetes (NKS)" icon="dharmachakra" href="/docs/platform-services/managed-kubernetes">
    Create and connect to an NKS cluster
  </Card>

  <Card title="Filesystem" icon="files" href="/docs/storage/file-storage">
    Create, attach, and configure file storage in the console
  </Card>

  <Card title="File storage CLI" icon="hard-drive" href="/docs/cli/filestorage">
    Manage file storage, storage classes, and snapshot policies from the command line
  </Card>

  <Card title="Kubernetes CLI" icon="terminal" href="/docs/cli/kubernetes">
    Manage clusters and kubeconfigs from the command line
  </Card>
</CardGroup>
