# csi-digitalocean: attaching block storage volumes to Kubernetes

> The DigitalOcean CSI driver is the component that makes a managed block storage volume appear as a persistent volume claim inside a cluster. The readme documents the features in detail, including the awkward parts, and it is unusually explicit about the three ways the driver can be deployed and which flags each of them requires.

**digitalocean/csi-digitalocean** — A Container Storage Interface (CSI) Driver  for DigitalOcean Block Storage

- Repository: https://github.com/digitalocean/csi-digitalocean
- Stars: 601 · Forks: 108
- Language: Go
- License: Apache-2.0
- Published: 2026-10-22 · Updated: 2026-10-22 · Language: en
- Canonical page: https://hysenlabs.com/projects/digitalocean-csi-digitalocean

## What a CSI driver is, and what this one covers

csi-digitalocean is a Container Storage Interface driver for DigitalOcean Block Storage, and the one-line summary in the readme is that it lets you use that storage with whichever container orchestrator you prefer.

The Container Storage Interface is the contract that makes that sentence possible. It is a specification, and an implementation of it is a plugin that a cluster talks to over a gRPC interface. The point of the specification is that the storage system does not have to know anything about Kubernetes and the orchestrator does not have to know anything about the storage system, so a volume from a cloud provider can be attached to a workload the same way a local disk is.

What the driver adds on top of the specification is the part that is specific to DigitalOcean: the cloud API calls to create, attach, detach, expand, snapshot, and delete volumes, and the code that mounts the attached device on the node.

The readme is honest about the scope of the testing. The plugin is mostly tested on Kubernetes, and in theory it should also work on other orchestrators such as Mesos or Cloud Foundry, and the author invites people to try it on those and give feedback. That is the correct way to state the position of a driver written for one orchestrator and aiming at the specification rather than at the implementation.

The features are listed against the specification, with the rule stated up front: in general, the features implementing an aspect of the specification are available on any DigitalOcean Kubernetes version for which beta support for the feature is provided.

So the driver does not decide on its own what is supported. The cluster version decides, because the beta support for a feature lands in a particular release, and the driver follows the cluster. That is a slightly unusual arrangement and it means the compatibility table in the readme is the thing to read first.

The versioning is plain semantic versioning with the rules spelled out: bug fixes are a patch release, new features such as a specification bump with no breaking changes are a minor release, and significant breaking changes are a major one. The current version is in the four series, with the last release in September 2026.

## Expanding a volume, and the three rules that catch people

Volume expansion is the first feature, and it is done the way the specification says: by changing the storage request on the claim.

```yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: csi-pvc
  namespace: default
spec:
  [...]
  resources:
    requests:
      # The field below can be increased.
      storage: 10Gi
      [...]
```

After a successful expansion, the status section of the claim reflects the actual capacity, which is the detail that matters for anything that reads the claim to find out how big the volume really is.

Then there are three notes, and they are the kind of note that saves an afternoon.

A volume can only be increased, never decreased, and an attempt to do so is an error rather than a silent no-op.

Expanding a volume to a size it already exceeds does nothing, and the status section keeps reporting the real capacity rather than the request. That is the honest behaviour: the request is a wish and the status is the truth, and when they disagree the status wins.

And the third one is the interesting one, because it is a warning about a different workflow. Resizing a volume outside the claim object, for example in the cloud control panel, is not recommended, because it can cause conflicts. The size change will not be reflected in the claim status immediately either, and the section will eventually show the real capacity.

The reason for that warning is a design consequence rather than an accident. The claim is the coordinator for the volume's lifecycle, so a second way of changing the size creates two sources of truth. The driver eventually reconciles, but the interval in between is a window in which the cluster and the storage system disagree, and a controller that resizes on its own schedule may act on the stale value.

The practical rule that follows is simple: change the size in one place, the claim, and let the driver do the rest.

## Raw block mode, snapshots, and the version that broke them

Raw block mode hands the pod the device rather than a file system, and it is enabled with a single field on the claim.

```yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: csi-pvc
  namespace: default
spec:
  [...]
  volumeMode: Block
```

The note attached to it is the one to read, and it concerns expansion. If you use the volume expansion feature, only the expansion of the underlying persistent volume is guaranteed. The driver does not guarantee that the file system on the device will be expanded automatically if you have formatted it.

That is the correct division of responsibility. A driver that knows the device and not the file system cannot safely grow a file system, because the operation depends on which file system is on there and which version wrote it. So the driver resizes the device, and the file system is your problem, which is the same rule the platform tools follow.

The snapshot feature has a long and painful history, and the readme documents it precisely.

Version 1 of the driver supported only the alpha version of the snapshot objects. Version 2 and 3 supported only the beta version. Version 4 and later support only the first stable version, which is backwards compatible with the beta one. But version 3 renders snapshots unusable that had previously been marked as invalid, and the readme points at the snapshotter documentation for the validating webhook and the upgrade notes.

That sentence is a warning rather than an explanation, and it is the kind of warning that only gets written after somebody hit it. The pattern is the usual one in the snapshot API: a validating webhook marks snapshot objects as invalid when the underlying snapshot is gone, and a change in how the driver handles those objects can make a whole class of them unusable.

The rest of the feature list is shorter. Volume statistics are exposed through the specification's endpoints, so a monitoring system such as Prometheus can scrape them and give insight into usage. And volumes can be transferred across clusters, with the steps written out in an example.

The transfer feature is the one that goes slightly beyond the specification, and it is worth understanding why it is useful: a volume is a piece of storage in a region, and moving it between clusters means a workload can be moved without the data being left behind.

## The three deployment modes, and the flags they need

The driver modes section is the part of the readme that is most likely to cost an hour if you skip it, because the flags you need depend on where the driver runs.

By default the driver supports both the controller mode and the node mode. It can manage the volumes through the cloud API and mount them on the required node, and which mode is actually used is determined by how the driver is deployed and configured. The suggested release manifests provide separate deployments for the two.

Then the three cases.

When you run the driver outside of DigitalOcean droplets, it can only function in controller mode. That requires setting the region flag to a valid DigitalOcean region slug in addition to the other flags. And the region flag must not be set when the driver is running on DigitalOcean droplets.

Alternatively the driver can run in node only mode on DigitalOcean droplets, in which case it handles only node related requests such as mounting a volume. The driver runs in node only mode when the token flag is not provided, and in that mode the secret creation step in the deployment instructions can be skipped, because no API token is needed.

The table that follows makes the three combinations explicit.

| Modes                                     |  `--token` flag  |  `--region` flag |
|-------------------------------------------|:----------------:|:----------------:|
| Controller and Node mode in DigitalOcean  |:white_check_mark:|        :x:       |
| Controller only mode not in DigitalOcean  |:white_check_mark:|:white_check_mark:|
| Node only mode in DigitalOcean            |        :x:       |        :x:       |

The logic behind it is straightforward once you see it. The token is what lets the driver call the cloud API, so a node only mode has no token because it never calls the API. The region is how the driver knows which region's API to call when it is running outside the cloud, and inside the cloud the region is discovered from the machine it runs on, so setting it explicitly is wrong.

The last point is the one that will trip somebody up, because the natural instinct is to be explicit about everything. Inside DigitalOcean, being explicit about the region is a mistake, and the readme says so with emphasis.

And the note about the managed Kubernetes product: it comes with the driver pre-installed, and no further steps are required. So the installation section is only relevant if you are running your own cluster, or running the driver on droplets.

## The compatibility table, and what it says about maintenance

The Kubernetes compatibility table is nineteen rows long, and reading it is a history of the project.

The first three releases, 1.19 through 1.21, are on the third version of the driver. Then 1.22 moves to the fourth. And from 1.23 onwards every row names a specific minimum, and the numbers climb steadily: 4.2, 4.3, 4.4, 4.5, 4.6, 4.7, 4.8, 4.9, then 4.12, 4.13, 4.14, 4.15, 4.16, 4.17, and finally 4.19 for 1.37.

Two things are visible in that progression. Each Kubernetes release needs a driver new enough for the specification it speaks, so the required minimum driver version climbs as the cluster releases advance. And the minor version jumps inside the four series are frequent, which tells you the driver ships often.

There is a gap in the sequence worth noticing: 4.10 and 4.11 do not appear. That is consistent with the patch and minor policy described earlier, where a specification bump with no breaking change is a minor release, and with the fact that some of the minor releases in that range carried no change relevant to the Kubernetes side.

The release cadence itself is regular: three releases in 2026 between May and September, each of them a minor version, and the last one on the same day as the last push.

The managed Kubernetes product note sits between the table and the modes section, and it is worth reading as a statement about who the audience is. If your cluster is the managed one, the driver is already there and none of this document applies to you. The whole readme is for the self-managed case.

And there is a line about the features being available where the cluster provides beta support, which links back to the first section. The driver does not decide which features it offers; the cluster does, and the driver's job is to implement them.

## The repository, and the build

The repository layout is the standard one for a Kubernetes sidecar plugin, and the directories are the parts of the driver.

There is a command directory with the plugin binary, a driver directory with the implementation, a deployment directory with the manifests, an examples directory with the Kubernetes examples, a scripts directory, a test directory, and a vendored dependency tree.

The vendoring is worth noting. A Kubernetes plugin runs as a container in a cluster, and vendoring the dependencies means the image it is built from does not fetch anything at build time and the exact versions are in the repository. For a component that runs with the permissions needed to attach storage to nodes, that is a reasonable trade.

The dependency list tells you what the driver is built on. The specification library itself, the DigitalOcean API client, a metadata library for the cloud instance, the gRPC library, the Kubernetes API machinery, the mount utilities, the logging libraries, the OAuth library for the token, a semantic version library, and the specification's own test suite as a development dependency. The test suite is the interesting one: the specification ships a conformance test harness, and a driver that passes it is a driver that another orchestrator could also use.

The Docker client appears as a dependency, which tells you the driver runs containers to do some of its work, most likely to run the file system formatting and resize operations on a node.

The build file is where the operational detail is. The name of the image is a variable, the Go version is read out of the module file rather than being repeated, and the linker flags stamp the version, the commit, and the state of the working tree into the binary, with the tree state computed by checking whether the working tree is clean. Stamping the dirtiness of the tree into the binary is a small thing that makes an incident much easier to diagnose, because you can tell from a running pod whether it was built from a clean tree.

The integration test section has the most interesting comment in the file. The maximum number of volumes attachable to a single droplet is seven, and the parallel integration count is set to seven to match it. That is a constraint from the cloud provider, and a test suite that ignored it would fail in a way that looks like a driver bug.

## Conclusion

csi-digitalocean fits a Kubernetes user on DigitalOcean who needs persistent volumes that survive a pod, and a team that wants to move a volume between clusters. It does not fit a user on another cloud, since the driver talks to the DigitalOcean API, and it does not fit somebody who expects the driver to manage the file system on the volume. Verify first which of the three deployment modes matches where you are running, since the required flags are different and one combination is explicitly forbidden, that the Kubernetes version you run is covered by the compatibility table, and that you read the notes on volume expansion, because resizing a volume outside the claim object is documented as a source of conflicts.

## FAQ

### What does the csi-digitalocean driver do?

It implements the Container Storage Interface for DigitalOcean Block Storage, so that a volume can be attached to a workload in a cluster through an ordinary persistent volume claim. It is mostly tested on Kubernetes, and in theory should also work with other orchestrators such as Mesos or Cloud Foundry.

### How do I expand a DigitalOcean volume with csi-digitalocean?

By increasing the storage request on the persistent volume claim. Volumes can only grow and never shrink, expanding a volume to a size it already exceeds does nothing, and the status section of the claim eventually reflects the real capacity. Resizing outside the claim object, for example in the control panel, is not recommended because it can cause conflicts.

### What are the deployment modes of the csi-digitalocean driver?

By default it supports both controller and node mode. Outside DigitalOcean droplets it can only run in controller mode, which requires the region flag. On droplets it can run in node only mode, which happens when the token flag is not given, and in that mode no API token is needed. The region flag must not be set when running on droplets.

### Which Kubernetes versions does csi-digitalocean support?

The readme has a table mapping each Kubernetes release from 1.19 to 1.37 to a minimum driver version, starting at the third version of the driver for the earliest releases and moving to the fourth series with a specific minor for every later release, up to 4.19 for Kubernetes 1.37.

### Does csi-digitalocean support volume snapshots?

Yes, through the snapshot objects, but the supported version changed over the life of the driver. The first version supported only the alpha objects, the second and third only the beta ones, and the fourth and later only the first stable version, which is backwards compatible with the beta one, with the caveat that the third version renders snapshots unusable that had previously been marked as invalid.

## Sources

- [digitalocean/csi-digitalocean on GitHub](https://github.com/digitalocean/csi-digitalocean)
- [Issues](https://github.com/digitalocean/csi-digitalocean/issues)
- [License: Apache-2.0](https://github.com/digitalocean/csi-digitalocean/blob/master/LICENSE)
- [README](https://github.com/digitalocean/csi-digitalocean/blob/master/README.md)
- [Releases](https://github.com/digitalocean/csi-digitalocean/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/digitalocean-csi-digitalocean
