You can enable the plugin only on clusters in regions where Network File Storage is available. The driver creates each share on the same VPC network as the cluster.
How to Dynamically Provision NFS Storage on Kubernetes Clustersprivate
Last verified 5 Oct 2026
DigitalOcean Kubernetes (DOKS) is a Kubernetes service with a fully managed control plane, high availability, and autoscaling. DOKS integrates with standard Kubernetes toolchains and DigitalOcean’s load balancers, volumes, CPU and GPU Droplets, API, and CLI.
DigitalOcean Kubernetes (DOKS) includes an optional NFS CSI plugin that installs the nfs.csi.digitalocean.com driver on your cluster. With the plugin enabled, you can create DigitalOcean Network File Storage (NFS) shares directly from Kubernetes by requesting storage with a PersistentVolumeClaim (PVC), the same way you add block storage volumes. The driver creates the share, and multiple pods across multiple nodes can then read and write to it simultaneously using the ReadWriteMany access mode.
The plugin is disabled by default. Without it, you can still use NFS shares with DOKS by statically provisioning them: you create the share yourself and write a PersistentVolume with its connection details. Static provisioning remains supported, and shares you created statically are unaffected by the plugin. You can enable or disable the plugin using doctl, the DigitalOcean API, Godo, or Terraform, and verify that it’s running with kubectl.
This feature is in private preview. To request access, contact Support, your account team, or your Customer Success team.
Feedback and Support
To give us feedback during the private preview, contact your Customer Success Manager or Solutions Architect.
Private preview releases may not be suitable for production workloads. We recommend avoiding sensitive workloads in preview features.
Your use of DigitalOcean services is governed by our terms of service, which cover eligibility, content, use, payments and billing, and warranties. This document is not a contract and all use of DigitalOcean’s services is subject to the terms of service.
Enable or Disable the Plugin
In the API, the plugin is controlled through the nfs_csi_plugin object on the Kubernetes cluster resource. Set enabled to true to turn the plugin on, or false to turn it off. Omitting the object from an update request leaves the plugin’s current state unchanged.
Disabling the plugin removes the driver from the cluster but does not delete any existing shares. Workloads that currently mount a dynamically provisioned share keep their mounts, but the cluster can no longer create, resize, or delete shares through PVCs until you re-enable the plugin.
To enable the plugin when creating a cluster, set the --enable-nfs-csi-plugin flag to true.
doctl kubernetes cluster create example-cluster --region nyc2 --version latest --enable-nfs-csi-plugin=trueTo enable or disable the plugin on an existing cluster, update the cluster with the same flag:
doctl kubernetes cluster update example-cluster --enable-nfs-csi-plugin=falseTo check whether the plugin is enabled on a cluster:
doctl kubernetes cluster get example-cluster --format NfsCsiPluginThe output shows whether the plugin is enabled:
Output
NFS CSI Plugin
trueTo enable the plugin when creating a cluster, send a POST request to https://api.digitalocean.com/v2/kubernetes/clusters with a request body similar to the following:
curl --location 'https://api.digitalocean.com/v2/kubernetes/clusters' \
--header 'Content-Type: application/json' \
--header "Authorization: Bearer $DIGITALOCEAN_TOKEN" \
--data '{
"name": "example-cluster",
"region": "nyc2",
"version": "1.36.1-do.0",
"node_pools": [
{
"size": "s-2vcpu-4gb",
"count": 3,
"name": "worker-pool"
}
],
"nfs_csi_plugin": {
"enabled": true
}
}'To enable or disable the plugin on an existing cluster, send a PUT request to https://api.digitalocean.com/v2/kubernetes/clusters/{cluster_id} with a request body similar to the following:
curl --location --request PUT 'https://api.digitalocean.com/v2/kubernetes/clusters/{cluster_id}' \
--header 'Content-Type: application/json' \
--header "Authorization: Bearer $DIGITALOCEAN_TOKEN" \
--data '{
"name": "example-cluster",
"nfs_csi_plugin": {
"enabled": true
}
}'Go developers can use Godo, the official DigitalOcean V2 API client for Go. To enable the plugin when creating a Kubernetes cluster with Godo, use code similar to the following:
package main
import (
"context"
"fmt"
"os"
"github.com/digitalocean/godo"
)
func main() {
client := godo.NewFromToken("your-digitalocean-token")
cluster, _, err := client.Kubernetes.Create(context.Background(), &godo.KubernetesClusterCreateRequest{
Name: "example-cluster",
RegionSlug: "nyc2",
VersionSlug: "1.36.1-do.0",
NodePools: []*godo.KubernetesNodePoolCreateRequest{
{
Name: "worker-pool",
Count: 3,
Size: "s-2vcpu-4gb",
},
},
NfsCsiPlugin: &godo.KubernetesNfsCsiPlugin{
Enabled: godo.PtrTo(true),
},
})
if err != nil {
fmt.Printf("Error creating cluster: %s\n", err)
os.Exit(1)
}
isEnabled := *cluster.NfsCsiPlugin.Enabled
fmt.Printf("Cluster creation successfully issued with NFS CSI plugin enabled=%v\n", isEnabled)
}To enable or disable the plugin on an existing cluster with Godo, use code similar to the following:
package main
import (
"context"
"fmt"
"os"
"github.com/digitalocean/godo"
)
func main() {
client := godo.NewFromToken("your-digitalocean-token")
cluster, _, err := client.Kubernetes.Update(context.Background(), "your-cluster-id", &godo.KubernetesClusterUpdateRequest{
Name: "example-cluster",
NfsCsiPlugin: &godo.KubernetesNfsCsiPlugin{
Enabled: godo.PtrTo(true),
},
})
if err != nil {
fmt.Printf("Error updating cluster: %s\n", err)
os.Exit(1)
}
isEnabled := *cluster.NfsCsiPlugin.Enabled
fmt.Printf("Cluster update successfully issued with NFS CSI plugin enabled=%v\n", isEnabled)
}To enable the plugin, add an nfs_csi_plugin block to the digitalocean_kubernetes_cluster resource:
resource "digitalocean_kubernetes_cluster" "example" {
name = "example-cluster"
region = "nyc2"
version = "1.36.1-do.0"
node_pool {
name = "worker-pool"
size = "s-2vcpu-4gb"
node_count = 3
}
nfs_csi_plugin {
enabled = true
}
}To disable the plugin, set enabled = false in the same block.
Verify the Plugin Is Running
When the plugin is enabled, DOKS runs the driver’s node component as a DaemonSet named csi-nfs-node in the kube-system namespace. To confirm it’s running on every node:
kubectl get daemonset csi-nfs-node -n kube-systemThe output looks similar to the following:
Output
NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE
csi-nfs-node 3 3 3 3 3 <none> 2mThe DESIRED and READY columns should match your node count.
The plugin also installs four storage classes. To list them:
kubectl get storageclassThe NFS storage classes appear alongside the default block storage classes:
Output
NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE
do-block-storage (default) dobs.csi.digitalocean.com Delete Immediate true 30m
do-nfs-high-performance nfs.csi.digitalocean.com Delete Immediate true 2m
do-nfs-high-performance-retain nfs.csi.digitalocean.com Retain Immediate true 2m
do-nfs-standard nfs.csi.digitalocean.com Delete Immediate true 2m
do-nfs-standard-retain nfs.csi.digitalocean.com Retain Immediate true 2mChoose a Storage Class
Each storage class maps to a Network File Storage performance tier and a reclaim policy that controls what happens to the share when you delete its PVC:
| Storage class | Performance tier | Minimum size | Reclaim policy |
|---|---|---|---|
do-nfs-standard |
Standard | 50 GiB | Delete |
do-nfs-standard-retain |
Standard | 50 GiB | Retain |
do-nfs-high-performance |
High Performance | 500 GiB | Delete |
do-nfs-high-performance-retain |
High Performance | 500 GiB | Retain |
With the Delete reclaim policy, deleting the PVC permanently destroys the NFS share, including all data and snapshots on it. This differs from the static provisioning workflow, where deleting Kubernetes objects never deletes the share. Use a -retain storage class if the data must outlive the claim.
With a Retain storage class, deleting the PVC leaves the share intact, and you continue to be billed for it until you delete the share yourself.
All four storage classes mount shares with NFS protocol version 4.1 and nconnect=8, which opens 8 parallel TCP connections to the NFS server to improve throughput.
Create a PersistentVolumeClaim
To provision a share, create a PVC that references one of the NFS storage classes. If you request less storage than the minimum size for the storage class’s performance tier, the driver provisions a share at the minimum size instead. Shares can be no larger than 32 TiB. Create the following config file named nfs-dynamic-pvc.yaml:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: nfs-dynamic-pvc
spec:
storageClassName: do-nfs-standard
accessModes:
- ReadWriteMany
resources:
requests:
storage: 50GiUse kubectl apply to create the PVC:
kubectl apply -f nfs-dynamic-pvc.yamlThe driver creates a new share named pvc- followed by a unique identifier, such as pvc-1ab2c3d4-e5f6-7890-abcd-ef1234567890, on the cluster’s VPC network. Provisioning a share takes a few minutes; the PVC stays in the Pending state until the share is active. To watch its progress:
kubectl get pvc nfs-dynamic-pvcOnce the share is active, the PVC’s status changes to Bound:
Output
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
nfs-dynamic-pvc Bound pvc-1ab2c3d4-e5f6-7890-abcd-ef1234567890 50Gi RWX do-nfs-standard 3mThe share also appears on the Network File Storage page in the control panel, and billing for it begins when it’s created. If the PVC stays Pending, run kubectl describe pvc nfs-dynamic-pvc and check the events; errors from the DigitalOcean API, such as reaching your account’s share limit, appear there.
Mount the PVC in Your Workload
Mount the PVC the same way as any other volume. Because Network File Storage shares enforce root squashing, workloads that write to the share must run as a non-root user; for details, see Run Workloads as a Non-Root User.
The following config file, pod-with-nfs.yaml, mounts the PVC to a pod running as UID and GID 1000, and writes the current date to a log file on the share every 5 seconds:
apiVersion: v1
kind: Pod
metadata:
name: nfs-test-pod
spec:
securityContext:
runAsUser: 1000
runAsGroup: 1000
volumes:
- name: my-nfs-share
persistentVolumeClaim:
claimName: nfs-dynamic-pvc
containers:
- name: my-app-container
image: busybox
command: ["/bin/sh", "-c", "while true; do date >> /data/test.log; sleep 5; done"]
volumeMounts:
- name: my-nfs-share
mountPath: "/data"Use kubectl apply to create the pod:
kubectl apply -f pod-with-nfs.yamlAny number of pods, on any node in the cluster, can mount the same PVC and read and write to the share simultaneously.
Resize a Share
The NFS storage classes support online volume expansion, so you can increase a share’s size without unmounting it or restarting workloads. To resize, update the storage request on the PVC:
kubectl patch pvc nfs-dynamic-pvc -p '{ "spec": { "resources": { "requests": { "storage": "100Gi" }}}}'Shares can only be increased in size, never decreased.
Delete a Share
How you delete a dynamically provisioned share depends on the storage class’s reclaim policy:
- With a
Deletestorage class, delete the PVC withkubectl delete pvc <your-pvc-name>. The driver destroys the share, including its data and snapshots, and billing stops. - With a
Retainstorage class, deleting the PVC leaves the share running. To stop billing, delete the share from the control panel or API.
The reclaim policy only applies when you delete a PVC on a running cluster. Destroying the cluster does not delete the cluster’s dynamically provisioned shares by default, regardless of their storage class’s reclaim policy. The shares appear in the cluster’s associated resources in the destroy flow, where you can select them for deletion along with the cluster; otherwise they remain in your account and continue to accrue charges.
You can select individual NFS shares for deletion from the control panel’s destroy flow, or delete all of a cluster’s associated resources, including its NFS shares, using doctl kubernetes cluster delete --dangerous or the API’s destroy_with_associated_resources/dangerous endpoint. Selectively deleting specific NFS shares by ID through doctl kubernetes cluster delete-selective or the API’s destroy_with_associated_resources/selective endpoint is not yet supported.