# Sharing Data with Volumes

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

*Volumes* are persistent storage devices of a *Space* that live independently of the *Workspaces* that mount them (read more [here](https://octelium.com/docs/cordium/latest/workspaces/volumes.md)). This example shows four common ways to use them in the `payments` *Space*: a build cache shared by ephemeral CI *Workspaces*, a read-only dataset shared by many *Workspaces*, handing artifacts off from one *Workspace* to another, and persisting the state of ephemeral AI agent *Workspaces* across runs.

First, a *Space* admin creates the *Volumes*. `SHARED` *Volumes* can be mounted by several running *Workspaces* at the same time and require a multi-writer storage backend, while `EXCLUSIVE` *Volumes* (the default) are mounted by one running *Workspace* at a time (read more [here](https://octelium.com/docs/cordium/latest/workspaces/volumes.md#access-modes)):

```bash
cordium create volume go-cache.payments.cordium --size 30000 --shared
cordium create volume fixtures.payments.cordium --size 50000 --shared
cordium create volume release-artifacts.payments.cordium --size 10000
cordium create volume triage-agent-state.payments.cordium --size 2000
```

## A Shared Build Cache

Ephemeral *Workspaces* start from a clean storage on every run, which is great for reproducibility but means that every run downloads the Go modules and rebuilds everything from scratch. Pointing the Go caches to a `SHARED` *Volume* makes every run, including concurrent ones, reuse the work of the previous ones. Go's caches are safe to share between concurrent processes:

```yaml
spec:
  image:
    registry:
      url: golang:1.26-bookworm
  repository:
    url: https://github.com/acme-corp/payments-api
    cloneOptions:
      depth: 1
      disableLazyUnshallow: true
  runtime:
    autoStop: true
    volumeMounts:
      - volumeRef:
          name: go-cache
        mountPath: /cache
    envVars:
      - key: GOMODCACHE
        value: /cache/mod
      - key: GOCACHE
        value: /cache/build
    tasks:
      - name: test
        type: ON_CREATE
        workingDir: /workspace/repo
        onFailure: ON_FAILURE_ABORT
        run: go test -race ./...
```

```bash
cordium create template ci.payments.cordium --file ci.yaml
cordium create ws --template ci.payments.cordium --ephemeral --start
```

The same approach works for `npm` (`npm_config_cache`), `pip` (`PIP_CACHE_DIR`), Cargo (`CARGO_HOME`), Gradle (`GRADLE_USER_HOME`) and Bazel (`--disk_cache`).

## A Read-Only Dataset

Large fixtures, datasets and model weights should be downloaded once rather than by every *Workspace*. A *Space* admin populates the `fixtures` *Volume* once, for example by uploading a local directory to a *Workspace* that mounts it read-write:

```bash
cordium create ws --template api-dev.payments.cordium --volume fixtures:/fixtures --start
cordium cp -r ./fixtures/ <WORKSPACE>:/fixtures/
```

Every other *Workspace* mounts it read-only, which guarantees that no test or agent can modify the shared data:

```yaml
spec:
  runtime:
    volumeMounts:
      - volumeRef:
          name: fixtures
        mountPath: /fixtures
        readOnly: true
```

## Handing Off Artifacts

A release pipeline can build its artifacts in one *Workspace* and verify them in another, with a different image or in a locked-down network environment, by passing them through a *Volume*. Since the two *Workspaces* run one after the other, an `EXCLUSIVE` *Volume* is enough. Here is the build step, which uses a `release` *Template* similar to the `ci` *Template* above:

```bash
cordium create ws --template release.payments.cordium --ephemeral --start \
  --volume release-artifacts:/artifacts
```

with the following task:

```yaml
spec:
  runtime:
    tasks:
      - name: build
        type: ON_CREATE
        workingDir: /workspace/repo
        onFailure: ON_FAILURE_ABORT
        run: |
          VERSION="$(git describe --tags --always)"
          mkdir -p "/artifacts/$VERSION"
          GOOS=linux GOARCH=amd64 go build -trimpath -o "/artifacts/$VERSION/payments-api-linux-amd64" ./cmd/server
          GOOS=linux GOARCH=arm64 go build -trimpath -o "/artifacts/$VERSION/payments-api-linux-arm64" ./cmd/server
          (cd "/artifacts/$VERSION" && sha256sum payments-api-* > SHA256SUMS)
          ln -sfn "$VERSION" /artifacts/latest
```

And the verification step, once the build *Workspace* has stopped, in a fresh *Workspace* that mounts the same *Volume* read-only:

```bash
cordium run --space payments.cordium --image debian:13-slim --ephemeral --rm \
  --volume release-artifacts:/artifacts:ro
```

```bash
cd /artifacts/latest && sha256sum -c SHA256SUMS
```

> **Note:**
>
> Starting a *Workspace* that mounts an `EXCLUSIVE` *Volume* fails while another running *Workspace* mounts it. When you orchestrate such steps from a script, wait for the previous *Workspace* to stop first, for example via the SDKs' `WaitUntilStopped`/`wait_until_stopped()` helpers.

## Persisting the State of Ephemeral Agents

Ephemeral *Workspaces* are the right default for AI agents, but some agents benefit from a memory that survives across runs, such as a triage agent that keeps notes about recurring issues, or a job that keeps checkpoints. An `EXCLUSIVE` *Volume* mounted under the home directory gives exactly that, while the rest of the *Workspace* is still discarded after each run. Here is the relevant part of such an agent *Workspace*'s spec:

```yaml
spec:
  isEphemeral: true
  runtime:
    autoStop: true
    volumeMounts:
      - volumeRef:
          name: triage-agent-state
        mountPath: /home/octelium/.agent-state
```

Since an `EXCLUSIVE` *Volume* cannot be mounted by two running *Workspaces* at once, this also guarantees that two runs of the agent never write to its state concurrently.
