Skip to content

A Volume is named storage that outlives the work that uses it. Every change is saved as a numbered revision, so you can see what changed, mount an earlier revision read-only, and never lose files to a stopped machine. You pay for the bytes stored, after your org’s included 10 GB.

volume.yaml
apiVersion: nodus.dev/v1
kind: Volume
metadata:
name: ex-volumes-put-get
spec:
accessMode: ReadWriteOnce
size: 1Gi
source: {upload: {}}
Terminal window
nodus apply -f volume.yaml # or: nodus create volume data --size 100Gi
nodus volume put ex-volumes-put-get ./data /data # uploads ./data and prints the new revision
nodus volume ls ex-volumes-put-get /data
nodus volume get ex-volumes-put-get /data/hello.txt ./out/
nodus volume put data ./archive.tar.gz /raw --extract
nodus volume rm data /raw/old.csv
nodus volume clear data # an empty revision; the Volume and its history stay

Uploads only send what changed: a second put of the same files moves almost nothing. An upload that started from an older revision than the latest is refused with 409 Conflict, so two people uploading at once never overwrite each other silently; run it again on top of the new revision.

put adds to what is there: a directory’s contents merge into the remote path, and a file lands at the remote path, or inside it when the remote path is a directory or ends in /. rm and clear also commit new revisions, so earlier revisions keep the removed files. get and ls read the latest revision, or the one --revision <n> names; get … - prints a file to standard output, and existing local files are kept unless --force. --extract expands a .zip, .tar, .tar.gz, .tgz or .tar.zst archive. It refuses an archive whose paths or links lead outside the remote path, or that has more than 10,000 members.

job.yaml
apiVersion: nodus.dev/v1
kind: Job
metadata:
name: ex-volumes-put-get
spec:
image: nodus/python:3.12
volumes:
- {volume: ex-volumes-put-get, mountPath: /mnt/vol, readOnly: true}
command: [cat, /mnt/vol/data/hello.txt]
accessMode Who can write How changes are saved
ReadWriteOnce (default) One attempt at a time On exit and every commitInterval (default 5 minutes)
ReadOnlyMany (default for imports) Nobody; any number of readers Revisions come from imports and uploads
ReadWriteMany Every worker Each worker’s changes are published when it commits or exits; the last writer of a path wins

A ReadWriteOnce Volume is held by one attempt at a time. Starting a second writer, or deleting the Volume while it is held, fails with 409 VolumeBusy, which names the holder; mount it with readOnly: true to read alongside. Functions and Agents with more than one worker need ReadOnlyMany or ReadWriteMany.

Mount an earlier revision with revision: <n> and readOnly: true. nodus get volume data shows the latest REVISION, USED and the current HOLDER; GET …/volumes/{name}/revisions lists the kept revisions (revisionHistoryLimit, default 10).

Set exactly one source; the import runs once on Nodus, billed as CPU time, and produces revision 1:

spec:
accessMode: ReadOnlyMany
size: 200Gi
maxCostUSD: "2.00"
source: {huggingface: {repo: meta-llama/Llama-3.1-8B, revision: 0e9e39f249a16976918f6564b8830bc894c89659, secret: hf-token}}
Source Fields
huggingface repo, revision (a commit or tag), files globs, secret holding HF_TOKEN
git repo, ref, lfs
url an https:// url, sha256, extract: Auto unpacks .zip, .tar, .tar.gz, .tgz and .tar.zst
s3 uri and an S3 Connection

The Volume is Pending while it imports and Ready after. A failed import shows Failed with reason ImportFailed and a message; fix the source or add credits, then run nodus request reimport volume/<name>.

vol = nodus.Volume.from_name("data", create_if_missing=True)
vol.put_file("./local.csv", "/train/local.csv")
print(vol.listdir("/train"))