A Container Storage Interface (CSI) driver for TrueNAS 25.10.0+, enabling dynamic provisioning of persistent volumes in Kubernetes using TrueNAS storage.
- NFS volumes - ReadWriteMany (RWX) access mode for shared storage
- iSCSI volumes - Block storage with ReadWriteOnce (RWO) and ReadWriteMany (RWX) access modes (RWX requires cluster filesystem like GFS2/OCFS2)
- NVMe-oF/TCP volumes - Block storage over NVMe over Fabrics (TCP) with optional DH-CHAP authentication
- Dynamic provisioning - Automatic volume creation and deletion
- Volume expansion - Online resize of volumes
- Snapshots and clones - CSI snapshot support for backup and cloning
- CHAP authentication - Secure iSCSI connections
- ZFS compression - LZ4, ZSTD, GZIP, and other algorithms
- ZFS encryption - Dataset-level encryption with key management
- Automatic snapshot scheduling - Periodic snapshots via StorageClass
- TrueNAS Websocket API - Uses the modern TrueNAS Websocket API
- TrueNAS SCALE 25.10.0+
- API access enabled
- At least one ZFS pool configured
- Kubernetes 1.26+
- For snapshots: snapshot-controller installed
- NFS volumes: No additional requirements
- iSCSI volumes:
open-iscsipackage installed on worker nodes - NVMe-oF volumes:
nvme_tcp/nvme_fabricskernel modules available on worker nodes (the node DaemonSet loads them); requires TrueNAS SCALE 25.10+ with the NVMe-oF target service enabled
-
Create an API key in TrueNAS
- Log into TrueNAS web UI
- Navigate to your profile → API Keys
- Create a new API key and copy it
-
Configure the driver
# Edit the deployment manifest vi deploy/truenas-csi-driver.yamlUpdate the ConfigMap with your TrueNAS connection details and the Secret with your API key.
-
Deploy the driver
kubectl apply -f deploy/truenas-csi-driver.yaml
-
Create a StorageClass and PVC
kubectl apply -f examples/storageclass-nfs.yaml kubectl apply -f examples/pvc-nfs.yaml
Install the snapshot controller (required for snapshot support):
kubectl apply -f https://raw.githubusercontent.com/kubernetes-csi/external-snapshotter/master/client/config/crd/snapshot.storage.k8s.io_volumesnapshotclasses.yaml
kubectl apply -f https://raw.githubusercontent.com/kubernetes-csi/external-snapshotter/master/client/config/crd/snapshot.storage.k8s.io_volumesnapshotcontents.yaml
kubectl apply -f https://raw.githubusercontent.com/kubernetes-csi/external-snapshotter/master/client/config/crd/snapshot.storage.k8s.io_volumesnapshots.yaml
kubectl apply -f https://raw.githubusercontent.com/kubernetes-csi/external-snapshotter/master/deploy/kubernetes/snapshot-controller/rbac-snapshot-controller.yaml
kubectl apply -f https://raw.githubusercontent.com/kubernetes-csi/external-snapshotter/master/deploy/kubernetes/snapshot-controller/setup-snapshot-controller.yaml- Edit
deploy/truenas-csi-driver.yamlwith your configuration - Apply the manifest:
kubectl apply -f deploy/truenas-csi-driver.yaml
# Check driver pods are running
kubectl get pods -n truenas-csi
# Verify CSI driver is registered
kubectl get csidriversThe default deployment manifest uses /var/lib/kubelet as the kubelet root directory. Some Kubernetes distributions use a different path. If your distribution uses a non-standard path, you must update the following in deploy/truenas-csi-driver.yaml before deploying:
- All
hostPathvalues containing/var/lib/kubelet - The
DRIVER_REG_SOCK_PATHenvironment variable - The
--kubelet-registration-pathargument - The
mountPathfor thekubelet-dirvolume mount on thecsi-nodecontainer
| Distribution | Kubelet Path |
|---|---|
| Standard Kubernetes | /var/lib/kubelet (default) |
| MicroK8s | /var/snap/microk8s/common/var/lib/kubelet |
| K3s | /var/lib/rancher/k3s/agent/kubelet |
Important: The
kubelet-dirmountPathmust match thehostPath. If they differ, NFS mounts will succeed inside the CSI container but will not propagate to kubelet, causing pods to see local storage instead of NFS.
MicroK8s runs inside a snap with its own mount namespace. For CSI mount propagation to work, the host root filesystem must have shared propagation before MicroK8s starts:
sudo mount --make-rshared /
microk8s startTo make this persistent across reboots, create a systemd unit:
sudo tee /etc/systemd/system/microk8s-mount-propagation.service <<EOF
[Unit]
Description=Ensure shared mount propagation for MicroK8s
Before=snap.microk8s.daemon-containerd.service
[Service]
Type=oneshot
ExecStart=/bin/mount --make-rshared /
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl enable microk8s-mount-propagation| Setting | Description | Example |
|---|---|---|
truenasURL |
WebSocket URL to TrueNAS API | wss://10.0.0.100/api/current |
truenasInsecure |
Skip TLS verification | true (for self-signed certs) |
defaultPool |
Default ZFS pool for volumes | tank |
nfsServer |
NFS server address | 10.0.0.100 |
iscsiPortal |
iSCSI portal address | 10.0.0.100:3260 |
nvmeofPortal |
NVMe-oF portal address (optional; auto-derived) | 10.0.0.100:4420 |
iscsiIQNBase |
Base IQN for iSCSI targets | iqn.2024-01.com.example |
| Parameter | Description | Values |
|---|---|---|
protocol |
Storage protocol | nfs, iscsi, nvmeof |
pool |
ZFS pool (overrides default) | pool name |
datasetPath |
Parent path for volume datasets, relative to the pool (no pool prefix, no leading/trailing /, no ..). If unset, volumes are created at the pool root (pool/<pvc-name>); e.g. k8s/iscsi → pool/k8s/iscsi/<pvc-name> |
relative path |
compression |
ZFS compression algorithm | OFF, LZ4, GZIP[-1|-9], ZSTD[-1..-9], ZLE, LZJB |
sync |
ZFS sync mode | STANDARD, ALWAYS, DISABLED |
sparse |
Thin-provision the ZVOL (iSCSI/NVMe-oF); default false |
true, false |
Delete-time behavior (optional): forceDelete (true/false) forces removal of
busy resources; deleteExtentsWithTarget (true/false, default true) removes
the iSCSI extent along with its target.
| Parameter | Description | Example |
|---|---|---|
nfs.hosts |
Allowed hosts | 10.0.0.0/8,192.168.1.0/24 |
nfs.networks |
Allowed networks | 10.0.0.0/8 |
nfs.mountOptions |
Client mount options | hard,nfsvers=4.1 |
nfs.mapAllUser |
NFS user mapping (default: root) |
postgres |
nfs.mapAllGroup |
NFS group mapping (default: wheel) |
postgres |
nfs.rootSquash |
Squash all access to the mapped user (default: true). Set false for no_root_squash so a pod fsGroup can chown the volume root — required for ownership-sensitive non-root workloads (e.g. PostgreSQL/CNPG) |
false |
By default an NFS share squashes all client access to a single user (mapall,
root:wheel). Ownership-sensitive workloads that run as a non-root user (such as
PostgreSQL/CloudNativePG) need to own their data directory, which mapall cannot
provide. Set nfs.rootSquash: "false" to switch the share to no_root_squash:
incoming root is preserved so the kubelet (via the driver's fsGroupPolicy: File)
can chown the volume root to the pod's fsGroup, and non-root UIDs are no longer
squashed. Requires the workload to set a pod securityContext.fsGroup. See
examples/storageclass-nfs-fsgroup.yaml.
| Parameter | Description | Values |
|---|---|---|
volblocksize |
ZVOL block size | 512, 1K, 2K, 4K, 8K, 16K, 32K, 64K, 128K |
iscsi.blocksize |
iSCSI logical block size | 512, 1024, 2048, 4096 |
iscsi.iqn-base |
Override the IQN base (auto-derived from the appliance's iscsi.global.basename by default) |
IQN string |
iscsi.initiators |
Allowed initiator IQNs | comma-separated |
iscsi.chapUser |
CHAP username | string |
iscsi.chapSecret |
CHAP password (12-16 chars) | string |
iscsi.chapPeerUser |
Mutual CHAP peer user | string |
iscsi.chapPeerSecret |
Mutual CHAP peer password | string |
iscsi.multipathEnabled |
Enable multipath for the session (node-side); default false |
true, false |
iscsi.persistentSessions |
Keep the iSCSI session persistent (node-side); default false |
true, false |
IPv4 only: iSCSI portals must be IPv4. The pinned
csi-lib-iscsimis-parses IPv6 portal addresses, so iSCSI staging fails on IPv6-only clusters — use NFS there. The driver fails fast with a clear error if an IPv6 iSCSI portal is configured.
NVMe-oF also uses the volblocksize parameter above. DH-CHAP authentication is optional.
| Parameter | Description | Values |
|---|---|---|
nvmeof.hostNQN |
Authorized host NQN (required for DH-CHAP) | nqn.2014-08.org.nvmexpress:uuid:... |
nvmeof.dhchapKey |
DH-CHAP host key | DHHC-1:00:... |
nvmeof.dhchapCtrlKey |
Mutual DH-CHAP controller key | DHHC-1:00:... |
nvmeof.dhchapHash |
DH-CHAP hash (default SHA-256) |
SHA-256, SHA-384, SHA-512 |
nvmeof.dhchapDHGroup |
DH group | 2048-BIT, 3072-BIT, 4096-BIT, 6144-BIT, 8192-BIT |
| Parameter | Description | Values |
|---|---|---|
snapshot.schedule |
Cron schedule (5 fields) | 0 0 * * * |
snapshot.retention |
Retention period | 1-365 |
snapshot.retentionUnit |
Retention unit | HOUR, DAY, WEEK, MONTH, YEAR |
snapshot.naming |
Naming schema | auto-%Y-%m-%d_%H-%M |
snapshot.recursive |
Include child datasets | true, false |
| Parameter | Description | Values |
|---|---|---|
encryption |
Enable encryption | true, false |
encryption.algorithm |
Encryption algorithm | AES-256-GCM, AES-128-CCM |
encryption.passphrase |
Passphrase (min 8 chars) | string |
encryption.key |
Hex-encoded key (64 chars) | string |
encryption.generateKey |
Auto-generate key | true, false |
See the examples/ folder for sample configurations:
storageclass-nfs.yaml- Basic NFS StorageClassstorageclass-nfs-compressed.yaml- NFS with ZSTD compressionstorageclass-nfs-fsgroup.yaml- NFS for ownership-sensitive non-root workloads (no_root_squash + pod fsGroup)storageclass-iscsi.yaml- Basic iSCSI StorageClassstorageclass-iscsi-chap.yaml- iSCSI with CHAP authenticationstorageclass-nvmeof.yaml- Basic NVMe-oF/TCP StorageClassstorageclass-nvmeof-dhchap.yaml- NVMe-oF with DH-CHAP authenticationstorageclass-encrypted.yaml- Encrypted storagepvc-nfs.yaml/pvc-iscsi.yaml/pvc-nvmeof.yaml- PVC examplespod-with-pvc.yaml- Pod using a PVCvolumesnapshotclass.yaml/volumesnapshot.yaml- Snapshot examples
make build# Build Alpine-based image (standard Kubernetes)
make docker-build
# Build UBI-based image (Red Hat OpenShift certification)
make build-ubi# Login to quay.io
docker login quay.io
# Push UBI image to quay.io/truenas_solutions
make push-ubi
# Push all images (driver, operator, bundle)
make push-allmake test| Image | Description |
|---|---|
ghcr.io/truenas/truenas-csi |
CSI driver (Alpine-based, for standard Kubernetes) |
quay.io/truenas_solutions/truenas-csi |
CSI driver (UBI-based, for Red Hat OpenShift) |
quay.io/truenas_solutions/truenas-csi-operator |
Kubernetes operator |
quay.io/truenas_solutions/truenas-csi-operator-bundle |
OLM bundle for OperatorHub |
For an interactive demonstration of all driver features using a local Kind cluster, see docs/demo.md.
The TrueNAS CSI Driver supports Red Hat OpenShift 4.20+ and is designed for OperatorHub distribution.
-
Install via OperatorHub
- Navigate to Operators > OperatorHub
- Search for "TrueNAS CSI"
- Click Install
-
Create credentials secret
apiVersion: v1 kind: Secret metadata: name: truenas-api-credentials namespace: truenas-csi stringData: api-key: "YOUR-API-KEY"
-
Create TrueNASCSI resource
apiVersion: csi.truenas.io/v1alpha1 kind: TrueNASCSI metadata: name: truenas spec: truenasURL: "wss://your-truenas-ip/api/current" credentialsSecret: "truenas-api-credentials" defaultPool: "tank" nfsServer: "your-truenas-ip"
- Installation Guide - Detailed installation steps
- Configuration Reference - CRD and StorageClass options
- Upgrade Guide - Upgrade procedures
- Cluster Setup Guide - Set up an OpenShift cluster on vSphere (agent-based install) for testing/certification
- Red Hat Certification Guide - Certification process and requirements
Interactive demo scripts are provided to test the CSI driver:
# Set TrueNAS connection details in deploy/truenas-csi-driver.yaml, then:
./demo-simple.sh# Set environment variables
export TRUENAS_IP=192.168.1.100
export TRUENAS_API_KEY=your-api-key
export TRUENAS_POOL=tank
# Run the demo
./demo-openshift.shBoth demos provide interactive menus to test NFS/iSCSI provisioning, volume expansion, snapshots, and cloning.
- Report issues: https://github.com/truenas/truenas-csi/issues
- Submit pull requests: https://github.com/truenas/truenas-csi/pulls
GNU General Public License 3.0