# Volumes

> Cordium documentation. Canonical page: <https://octelium.com/docs/cordium/latest/workspaces/volumes>.

A *Volume* is a persistent storage device that belongs to a *Space* and that can be mounted by the *Workspaces* of that *Space* at arbitrary paths. Unlike a *Workspace*'s own storage, a *Volume* has a lifecycle of its own: it outlives the *Workspaces* that mount it, it can be mounted by ephemeral *Workspaces*, and it is shared among the *Members* of its *Space*. *Volumes* are typically used for:

- Build and dependency caches shared by ephemeral CI and AI agent *Workspaces* (e.g. Go, npm, pip, Cargo and Bazel caches).
- Datasets, model weights and fixtures that are too large to download on every run.
- Handing off artifacts between *Workspaces* (e.g. a build *Workspace* that produces binaries consumed by test *Workspaces*).
- Persisting the state of ephemeral *Workspaces* (e.g. an AI agent's memory or a long-running job's checkpoints).

## Creating Volumes

*Volumes* are created by the admins of their *Space*:

```bash
# A 50 GB Volume in the payments Space
cordium create volume datasets.payments.cordium --size 50000

# A Volume that can be mounted by several Workspaces at the same time
cordium create volume go-cache.payments.cordium --size 20000 --shared

# A Volume in a specific Region
cordium create volume models.research.cordium --size 200000 --region eu-west
```

You can list and inspect the *Volumes* of a *Space* as follows:

```bash
cordium get volume --space payments.cordium
cordium get volume datasets.payments.cordium -o yaml
```

| Field                 | Description                                                                                                                                            |
| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `spec.size.megabytes` | The requested capacity. It defaults to 10 GB, must be at least 1000 MB, and is capped by the `ClusterConfig`. It can be grown later, but never shrunk. |
| `spec.accessMode`     | `ACCESS_MODE_EXCLUSIVE` (the default) or `ACCESS_MODE_SHARED`. It is immutable.                                                                        |
| `status.state`        | `STATE_PENDING`, `STATE_READY` or `STATE_FAILED`.                                                                                                      |
| `status.regionRef`    | The *Region* that hosts the *Volume*'s storage.                                                                                                        |
| `status.capacity`     | The actual capacity of the provisioned storage, which can be larger than requested.                                                                    |

> **Note:**
>
> *Volumes* can already be mounted while they are `STATE_PENDING`, since most storage backends only provision the storage once the first *Workspace* that uses it is scheduled.

## Access Modes

**`ACCESS_MODE_EXCLUSIVE`** *Volumes* can only be mounted by one active *Workspace* at a time. Starting a second *Workspace* that mounts the same *Volume* fails until the first one stops. They are backed by single-writer storage, which is what block storage backends (e.g. Longhorn, EBS, Persistent Disk or local volumes) provide. Use them for state that belongs to one *Workspace* at a time.

**`ACCESS_MODE_SHARED`** *Volumes* can be mounted by any number of *Workspaces* concurrently, even across nodes. They require the *Cluster* to be configured with a multi-writer filesystem storage backend (e.g. NFS, CephFS, EFS or Filestore). Otherwise, their storage cannot be provisioned and the *Workspaces* that mount them cannot start (read more [here](https://octelium.com/docs/cordium/latest/management/storage.md)). Use them for shared caches and datasets.

## Mounting Volumes

*Volumes* are mounted via the `spec.runtime.volumeMounts` field of a *Workspace* or a *Template*, by their name. A *Workspace* can mount up to 16 *Volumes*:

```yaml
spec:
  runtime:
    volumeMounts:
      - volumeRef:
          name: go-cache
        mountPath: /cache/go
      - volumeRef:
          name: datasets
        mountPath: /data
        readOnly: true
```

Or via the `--volume` flag of `cordium run` and `cordium create workspace` in the `NAME:MOUNT_PATH[:ro]` format:

```bash
cordium run --template api-dev.payments.cordium \
  --volume go-cache:/cache/go \
  --volume datasets:/data:ro
```

Keep the following in mind:

- The *Volume* must belong to the same *Space* as the *Workspace*, and all the *Volumes* mounted by a *Workspace* must be hosted in the same *Region*. The *Workspace* is automatically scheduled in that *Region*.
- The mount path must be an absolute path that does not overlap with another mount. It cannot be `/workspace` itself nor a system directory (e.g. `/`, `/etc`, `/usr`, `/var`, `/tmp`, `/proc`, `/dev`, `/run`, `/sys` or the container storage directories), but it can be a path under `/workspace` or under the home directory. If the path already exists inside the *Workspace*, it must be an empty directory.
- `readOnly` is set per mount, which means that the same *Volume* can be mounted read-write by one *Workspace* and read-only by others.
- A newly created *Volume* is owned by the *Workspace* user of the first *Workspace* that mounts it read-write.
- Mounts are applied on every start, which means that you can add or remove the mounts of a persistent *Workspace* by updating its spec.

## Using Volumes as Caches

A common pattern is to point the package managers and build tools of ephemeral *Workspaces* to a shared *Volume*, so that every run starts with a warm cache. Here is an example of a *Template* for ephemeral Go CI runs:

```yaml
spec:
  image:
    registry:
      url: golang:1.26-bookworm
  runtime:
    volumeMounts:
      - volumeRef:
          name: go-cache
        mountPath: /cache
    envVars:
      - key: GOMODCACHE
        value: /cache/mod
      - key: GOCACHE
        value: /cache/build
    autoStop: true
    tasks:
      - name: test
        type: ON_CREATE
        workingDir: /workspace/repo
        run: go test ./...
        onFailure: ON_FAILURE_ABORT
```

You can read more examples of using *Volumes* between *Workspaces* [here](https://octelium.com/docs/cordium/latest/examples/workflows/volumes.md).

## Resizing and Deleting Volumes

A *Volume* can be grown via the `UpdateVolume` API or the web portal, provided that the storage backend supports volume expansion. A *Volume* can be deleted as follows:

```bash
cordium delete volume datasets.payments.cordium
```

A *Volume* cannot be deleted while it is referenced by a *Workspace* or a *Template* of its *Space*. Remove the mounts first.

> **Note:**
>
> *Volumes* are never included in *WorkspaceSnapshots* or *Template* pre-builds. A *Workspace* restored from a snapshot mounts the current content of its *Volumes* like any other *Workspace*.
