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.

Note

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.

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=true

To 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=false

To check whether the plugin is enabled on a cluster:

doctl kubernetes cluster get example-cluster --format NfsCsiPlugin

The output shows whether the plugin is enabled:

Output
NFS CSI Plugin
true

To 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-system

The 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>          2m

The DESIRED and READY columns should match your node count.

The plugin also installs four storage classes. To list them:

kubectl get storageclass

The 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                   2m

Choose 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
Warning

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: 50Gi

Use kubectl apply to create the PVC:

kubectl apply -f nfs-dynamic-pvc.yaml

The 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-pvc

Once 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   3m

The 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.yaml

Any 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 Delete storage class, delete the PVC with kubectl delete pvc <your-pvc-name>. The driver destroys the share, including its data and snapshots, and billing stops.
  • With a Retain storage 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.

Note

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.

We can't find any results for your search.

Try using different keywords or simplifying your search terms.