Volumes#

Volumes offer a high-performance alternative to cloud buckets for storing data. Unlike buckets, volumes are limited to a single region and cannot be accessed across regions and clouds.

Benefits of using volumes:

  • Performance: Volumes are generally faster than object stores, and SkyPilot lets you choose from different storage classes based on your performance requirements.

  • Data persistence: Volumes can persist data independently of task life cycles, making it easy to share data between different tasks (e.g., datasets, caches) or preserve results.

  • Size control: You can set volume size limits to manage costs and limit storage usage.

Volumes are currently supported on Kubernetes clusters and RunPod.

Quickstart#

  1. Prepare a volume YAML file:

    # volume.yaml
    name: new-pvc
    type: k8s-pvc
    infra: k8s  # or `k8s/context` or `runpod`
    size: 10Gi
    
    # Optional: To use an existing PVC on k8s instead of creating a new one, set to `true` and set `name` to the existing PVC name.
    # use_existing: true
    
  2. Create the volume with sky volumes apply volume.yaml:

    $ sky volumes apply volume.yaml
    Proceed to create volume 'new-pvc'? [Y/n]: Y
    Creating PVC: new-pvc-73ec42f2-5c6c4e
    
  3. Mount the volume in your task YAML:

    # task.yaml
    volumes:
      /mnt/data: new-pvc  # The volume new-pvc will be mounted to /mnt/data
    
    run: |
      echo "Hello, World!" > /mnt/data/hello.txt
    

Tip

For temporary or cache data that should only last for the lifetime of a SkyPilot cluster, use ephemeral volumes.

Managing volumes#

List all volumes with sky volumes ls:

$ sky volumes ls -v
NAME     TYPE     INFRA                         SIZE  USER   WORKSPACE  AGE   STATUS  LAST_USE             USED_BY   NAME_ON_CLOUD              STORAGE_CLASS           ACCESS_MODE
new-pvc  k8s-pvc  Kubernetes/nebius-mk8s-vol    1Gi   alice  default    8m    IN_USE  2025-06-24 10:18:32  training  new-pvc-73ec42f2-5c6c4e    csi-mounted-fs-path-sc  ReadWriteMany

Delete a volume with sky volumes delete:

$ sky volumes delete new-pvc
Proceed to delete volume 'new-pvc'? [Y/n]: Y
Deleting PVC: new-pvc-73ec42f2-5c6c4e

If the volume is in use, it will be marked as IN_USE and cannot be deleted.

You can also check the volumes in the SkyPilot dashboard.

SkyPilot volumes

Volumes on Kubernetes#

In Kubernetes clusters, SkyPilot Volumes map to PVCs (Persistent Volume Claims).

PVCs can be backed by various storage backends, including block storage solutions (AWS EBS, GCP Persistent Disk) and distributed file systems (JuiceFS, Nebius shared file system, AWS EFS, GCP Filestore).

SkyPilot Volumes can be of two types:

  1. Persistent volumes: Managed through sky volumes CLI commands with lifecycle separate from SkyPilot clusters.

  2. Ephemeral volumes: Bound to SkyPilot cluster lifecycle, automatically created and deleted when sky launch or sky down is run.

Feature

Persistent volumes

Ephemeral volumes

Lifecycle

Independent (managed via sky volumes)

Bound to SkyPilot cluster

Creation

sky volumes apply

Automatic (in task YAML)

Deletion

sky volumes delete

Automatic (with cluster)

Sharing across SkyPilot clusters

Yes

No (cluster-specific)

Use case

Long-term data, code, shared datasets

Temporary storage, caches

Tip

For advanced use cases, you can also mount PVCs, NFS, or hostPath volumes by overriding SkyPilot’s pod configs. See Advanced: Use Kubernetes configs to mount PVCs, NFS, or hostPath for details.

Persistent volumes#

Persistent volumes are created and managed independently using the sky volumes CLI commands described in the Quickstart and Managing volumes sections above.

Note

Persistent volumes are shared across users on a SkyPilot API server. A user can mount volumes created by other users. This is useful for sharing caches and data across users.

Volume YAML configuration options:

# volume.yaml
name: my-volume
type: k8s-pvc
infra: k8s  # or k8s/<context>
size: 10Gi

# Optional: To use an existing PVC instead of creating a new one, set to `true` and set `name` to the existing PVC name.
use_existing: true

# Optional: add labels to the PVC
labels:
  key: value

# Optional: additional configuration
config:
  namespace: default
  storage_class_name: csi-mounted-fs-path-sc
  access_mode: ReadWriteMany  # Required for multi-node clusters

Note

  • For multi-node clusters, volumes are mounted to all nodes. You must set config.access_mode to ReadWriteMany and use a storage_class_name that supports this access mode. Otherwise, SkyPilot will fail to launch the cluster.

  • To mount a volume to all clusters or jobs by default, use the admin policy to inject the volume path into the task YAML. See Add volumes to all tasks for details.

Ephemeral volumes#

Unlike persistent volumes, which must be managed independently via sky volumes CLI commands, ephemeral volumes are automatically created when a cluster is launched via sky launch and deleted when the cluster is terminated via sky down or autodowned.

  • Automatic lifecycle management: No need to manually create or delete volumes

  • Cluster-bound: Created with the cluster and deleted when the cluster is terminated

  • Simplified usage: Defined directly in the task YAML with the cluster configuration

  • Ideal for temporary storage: caches, intermediate results, or any data that should only exist for the duration of a cluster’s lifetime

To use an ephemeral volume, simply specify the size field in the volumes section of your task YAML:

# task.yaml
resources:
  infra: kubernetes
  ...

volumes:
  /mnt/cache:
    size: 100Gi
    #labels:        # optional
    #  env: production
    #config:        # optional
    #  storage_class_name: csi-mounted-fs-path-sc  # optional
    #  access_mode: ReadWriteMany                  # optional

run: |
  echo "Using ephemeral volumes"
  df -h /mnt/cache
  echo "data" > /mnt/cache/temp.txt

When you launch the cluster with sky launch, the ephemeral volumes will be automatically created:

$ sky launch -c my-cluster task.yaml
# Ephemeral volumes are created automatically
$ sky volumes ls
Kubernetes PVCs:
NAME                       TYPE     INFRA                      SIZE  USER  WORKSPACE  AGE   STATUS  LAST_USE             USED_BY                  IS_EPHEMERAL
my-cluster-43dbb4ab-2f74bf k8s-pvc  Kubernetes/nebius-mk8s-vol 100Gi alice default    58m   IN_USE  2025-11-17 14:30:18  my-cluster-43dbb4ab-head True

Note

For multi-node clusters, ephemeral volumes are mounted to all nodes. You must configure config.access_mode to ReadWriteMany and use a storage_class_name that supports the ReadWriteMany access mode. Otherwise, SkyPilot will fail to launch the cluster.

When you terminate the cluster, the ephemeral volumes are automatically deleted:

$ sky down my-cluster
# Cluster and its ephemeral volumes are deleted

Advanced: Use Kubernetes configs to mount PVCs, NFS, or hostPath#

In addition to using SkyPilot volumes, you can also mount Kubernetes volumes (PVCs, NFS, hostPath) by overriding SkyPilot’s pod_config. This is useful for:

  1. Mounting a PVC with additional configurations not supported by SkyPilot volumes (e.g., fsGroup, fsGroupChangePolicy).

  2. Specifying a global (per Kubernetes context) volume to be mounted on all SkyPilot clusters.

  3. Accessing shared storage such as NFS or local high-performance storage like NVMe drives.

Volume mounting can be done directly in the task YAML on a per-task basis, or globally for all tasks in SkyPilot config.

Mount a NFS share that’s already mounted on the Kubernetes nodes.

Per-task configuration:

# task.yaml
run: |
  echo "Hello, world!" > /mnt/nfs/hello.txt
  ls -la /mnt/nfs
config:
  kubernetes:
    pod_config:
      spec:
        containers:
          - volumeMounts:
              - mountPath: /mnt/nfs
                name: my-host-nfs
        volumes:
          - name: my-host-nfs
            hostPath:
              path: /path/on/host/nfs
              type: Directory

Global configuration:

# SkyPilot config
kubernetes:
  pod_config:
    spec:
      containers:
        - volumeMounts:
            - mountPath: /mnt/nfs
              name: my-host-nfs
      volumes:
        - name: my-host-nfs
          hostPath:
            path: /path/on/host/nfs
            type: Directory

Mount a NFS share using Kubernetes’ native NFS volume support.

Per-task configuration:

# task.yaml
run: |
  echo "Hello, world!" > /mnt/nfs/hello.txt
  ls -la /mnt/nfs
config:
  kubernetes:
    pod_config:
      spec:
        containers:
          - volumeMounts:
              - mountPath: /mnt/nfs
                name: nfs-volume
        volumes:
          - name: nfs-volume
            nfs:
              server: nfs.example.com
              path: /shared
              readOnly: false

Global configuration:

# SkyPilot config
kubernetes:
  pod_config:
    spec:
      containers:
        - volumeMounts:
            - mountPath: /mnt/nfs
              name: nfs-volume
      volumes:
        - name: nfs-volume
          nfs:
            server: nfs.example.com
            path: /shared
            readOnly: false

Mount local NVMe storage that’s already mounted on the Kubernetes nodes.

Per-task configuration:

# task.yaml
run: |
  echo "Hello, world!" > /mnt/nvme/hello.txt
  ls -la /mnt/nvme
config:
  kubernetes:
    pod_config:
      spec:
        containers:
          - volumeMounts:
              - mountPath: /mnt/nvme
                name: nvme
        volumes:
          - name: nvme
            hostPath:
              path: /path/on/host/nvme
              type: Directory

Global configuration:

# SkyPilot config
kubernetes:
  pod_config:
    spec:
      containers:
        - volumeMounts:
            - mountPath: /mnt/nvme
              name: nvme
      volumes:
        - name: nvme
          hostPath:
            path: /path/on/host/nvme
            type: Directory

Mount a PVC with additional configurations like fsGroup and fsGroupChangePolicy.

Per-task configuration:

# task.yaml
run: |
  echo "Hello, world!" > /mnt/data/hello.txt
  ls -la /mnt/data
config:
  kubernetes:
    pod_config:
      spec:
        securityContext:
          fsGroup: 1000
          fsGroupChangePolicy: OnRootMismatch
        containers:
          - volumeMounts:
              - mountPath: /mnt/data
                name: my-pvc
        volumes:
          - name: my-pvc
            persistentVolumeClaim:
              claimName: my-pvc

Global configuration:

# SkyPilot config
kubernetes:
  pod_config:
    spec:
      securityContext:
        fsGroup: 1000
        fsGroupChangePolicy: OnRootMismatch
      containers:
        - volumeMounts:
            - mountPath: /mnt/data
              name: my-pvc
      volumes:
        - name: my-pvc
          persistentVolumeClaim:
            claimName: my-pvc

Mount different PVCs per context:

If you want to mount different PVCs for different Kubernetes contexts, you can set the allowed_contexts and context_configs in the advanced config.

# SkyPilot config
kubernetes:
  allowed_contexts:
    - context1
    - context2
  context_configs:
    context1:
      pod_config:
        spec:
          securityContext:
            fsGroup: 1000
            fsGroupChangePolicy: OnRootMismatch
          containers:
            - volumeMounts:
                - mountPath: /mnt/data
                  name: my-pvc
          volumes:
            - name: my-pvc
              persistentVolumeClaim:
                claimName: pvc1
    context2:
      pod_config:
        spec:
          securityContext:
            fsGroup: 1000
            fsGroupChangePolicy: OnRootMismatch
          containers:
            - volumeMounts:
                - mountPath: /mnt/data
                  name: my-pvc
          volumes:
            - name: my-pvc
              persistentVolumeClaim:
                claimName: pvc2

Note

The kubernetes.pod_config in the advanced config applies to every cluster launched on Kubernetes. To mount different PVCs per cluster, set the kubernetes.pod_config in the task YAML file as described in the per-task configuration. Refer to Kubernetes volume mounts and volumes documentation for more details.

When creating a node group on the Nebius console, attach your desired shared file system to the node group (Create Node Group -> Attach shared filesystem):

  • Ensure Auto mount is enabled.

  • Note the Mount tag (e.g. filesystem-d0).

../_images/nebius-k8s-attach-fs.png

Nebius will automatically mount the shared filesystem to hosts in the node group. You can then use a hostPath volume to mount the shared filesystem to your SkyPilot pods.

Per-task configuration:

# task.yaml
run: |
  echo "Hello, world!" > /mnt/nfs/hello.txt
  ls -la /mnt/nfs
config:
  kubernetes:
    pod_config:
      spec:
        containers:
          - volumeMounts:
              - mountPath: /mnt/nfs
                name: nebius-sharedfs
        volumes:
          - name: nebius-sharedfs
            hostPath:
              path: /mnt/<mount_tag> # e.g. /mnt/filesystem-d0
              type: Directory

Global configuration:

# SkyPilot config
kubernetes:
  pod_config:
    spec:
      containers:
        - volumeMounts:
            - mountPath: /mnt/nfs
              name: nebius-sharedfs
      volumes:
        - name: nebius-sharedfs
          hostPath:
            path: /mnt/<mount_tag> # e.g. /mnt/filesystem-d0
            type: Directory

Note

When using hostPath volumes, the specified paths must already exist on the Kubernetes node where the pod is scheduled.

For NFS mounts using hostPath, ensure the NFS mount is already configured on all Kubernetes nodes.

Advanced: Installing additional storage backends#

SkyPilot volumes work with any Kubernetes StorageClass already available in your cluster. If your cluster doesn’t have a StorageClass that meets your needs, you can optionally install one.

Below are example configurations for setting up shared filesystems like JuiceFS or Nebius Shared Filesystem as SkyPilot volumes. Any storage backend that provides a Kubernetes StorageClass will work.

Installing additional storage backends - JuiceFS, Nebius Shared Filesystem

To use JuiceFS as a SkyPilot volume:

  1. Install the JuiceFS CSI driver on your Kubernetes cluster. Follow the official installation guide for detailed instructions.

  2. Verify the driver installation - Confirm that the JuiceFS CSI Driver pods are running:

$ kubectl -n kube-system get pod -l app.kubernetes.io/name=juicefs-csi-driver
NAME                       READY   STATUS    RESTARTS   AGE
juicefs-csi-controller-0   2/2     Running   0          10m
juicefs-csi-node-8rd96     3/3     Running   0          10m
  1. Set up JuiceFS storage and create a SkyPilot volume - You can use either dynamic provisioning (with a StorageClass) or static provisioning (with a pre-created PV):

Create a StorageClass for dynamic provisioning. Refer to the JuiceFS StorageClass guide for details.

$ kubectl get storageclass juicefs-sc
NAME         PROVISIONER       RECLAIMPOLICY   VOLUMEBINDINGMODE   ALLOWVOLUMEEXPANSION   AGE
juicefs-sc   csi.juicefs.com   Retain          Immediate           false                  10m

Create a SkyPilot volume YAML referencing the StorageClass:

# juicefs-volume.yaml
name: juicefs-volume
type: k8s-pvc
infra: k8s
size: 100Gi
config:
  storage_class_name: juicefs-sc
  access_mode: ReadWriteMany
$ sky volumes apply juicefs-volume.yaml

Create a PersistentVolume and PVC manually. Refer to the JuiceFS static provisioning guide for details.

$ kubectl get pv juicefs-pv
NAME         CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                 STORAGECLASS   AGE
juicefs-pv   100Gi      RWX            Retain           Bound    default/juicefs-pvc                  10m

$ kubectl get pvc juicefs-pvc
NAME          STATUS   VOLUME       CAPACITY   ACCESS MODES   STORAGECLASS   AGE
juicefs-pvc   Bound    juicefs-pv   100Gi      RWX                           10m

Create a SkyPilot volume YAML with use_existing: true to reference the existing PVC:

# juicefs-volume.yaml
name: juicefs-volume
type: k8s-pvc
infra: k8s
use_existing: true
config:
  access_mode: ReadWriteMany
$ sky volumes apply juicefs-volume.yaml
  1. Mount the volume to SkyPilot task in your SkyPilot YAML:

# task.yaml
num_nodes: 2

volumes:
  # Mount the JuiceFS volume to /mnt/data across all nodes
  /mnt/data: juicefs-volume

run: |
  # Verify the volume is mounted and accessible
  df -h /mnt/data
  ls -la /mnt/data
# Launch the cluster with the JuiceFS volume
$ sky launch -c juicefs-cluster task.yaml

To use Nebius shared file system as a SkyPilot volume using the CSI driver. For a simpler setup, we recommend using the hostPath-based method described above, which mounts the filesystem directly from the host without requiring a CSI driver.

  1. Set up the Nebius filesystem infrastructure by following the official documentation:

  2. Verify the storage class - Confirm that the csi-mounted-fs-path-sc storage class has been created:

$ kubectl get storageclass
NAME                     PROVISIONER                    RECLAIMPOLICY   VOLUMEBINDINGMODE      ALLOWVOLUMEEXPANSION   AGE
csi-mounted-fs-path-sc   mounted-fs-path.csi.nebius.ai  Delete          WaitForFirstConsumer   false                  10m
  1. Create a SkyPilot volume for Nebius file system with a volume YAML:

# nebius-volume.yaml
name: nebius-pvc
type: k8s-pvc
infra: k8s
size: 100Gi
config:
  storage_class_name: csi-mounted-fs-path-sc
  access_mode: ReadWriteMany
$ sky volumes apply nebius-volume.yaml
  1. Mount the volume to SkyPilot task in your SkyPilot YAML:

# task.yaml
num_nodes: 2

volumes:
  # Mount the Nebius shared filesystem to /mnt/data across all nodes
  /mnt/data: nebius-pvc

run: |
  # Verify the volume is mounted and accessible
  df -h /mnt/data
  ls -la /mnt/data
# Launch the cluster with the Nebius volume
$ sky launch -c nebius-cluster task.yaml

Volumes on RunPod#

RunPod Network Volumes provide persistent storage that can be mounted into pods on RunPod. SkyPilot supports creating and managing RunPod network volumes via the same three commands:

  • sky volumes apply: Create a new network volume

  • sky volumes ls: List all volumes

  • sky volumes delete: Delete a volume

Notes specific to RunPod:

  • infra must specify the RunPod data center (zone), e.g. runpod/CA/CA-MTL-1.

  • Volume name length is limited (max 30 characters).

  • Labels are not currently supported for RunPod volumes.

Quickstart#

  1. Prepare a volume YAML file:

    # runpod-volume.yaml
    name: rpvol
    type: runpod-network-volume
    infra: runpod/CA/CA-MTL-1  # DataCenterId (zone)
    size: 100Gi                # GiB
    # If the RunPod network volume already exists, set `use_existing` to true and
    # set the `name` to the existing RunPod network volume name
    # use_existing: true
    
  2. Create the volume with sky volumes apply runpod-volume.yaml:

    $ sky volumes apply runpod-volume.yaml
    Proceed to create volume 'rpvol'? [Y/n]: Y
    Created RunPod network volume rpvol-43dbb4ab-15e906 (id=5w6ecp2w9n)
    
  3. Mount the volume in your task YAML:

    # task.yaml
    volumes:
      /workspace: rpvol
    
    run: |
      echo "Hello, RunPod!" > /workspace/hello.txt
    

Managing volumes#

Same as Kubernetes volumes, refer to Managing volumes for more details.

Volumes on SSH node pools#

With SSH node pools, you can mount host volumes or directories into SkyPilot clusters and managed jobs. See Volumes on SSH node pools for details.