Disks
Persistent directories for dependency stores, build output, and reference data across StarSling Runner jobs
StarSling Disks are persistent filesystems mounted into StarSling Runners as ordinary directories. Jobs read and write files in place, without packing, uploading, downloading, or extracting an archive. Use them for dependency stores, build output, toolchains, datasets, and reference data shared across a repository's jobs.
Each disk has a name scoped to its repository. A job can mount several disks, with one Disks action step per disk. Many jobs can read the same published generation concurrently; each job's writes stay private until a successful publication makes them available to later jobs.
Disks are separate from the disk size on a runner label. Those tables describe the runner's local disk. See Compute Specifications.
The Disks action is being prepared for release in its public GitHub repository. The examples below use the planned starslingdev/disks@v0 release. Your organization must be enrolled for Disks. Once released, pin a full commit SHA for production workflows.
Get started
Use a StarSling runner label, grant the job id-token: write, and put the Disks step before the tool writes to the target directory. The OIDC token lets StarSling verify the job; the action does not need a separate StarSling API token.
This workflow keeps pnpm's store warm. It asks to publish from the repository's default branch, while pull requests and other branches use the published contents and discard their own writes.
name: Build
on: [push, pull_request]
jobs:
build:
runs-on: starsling-ubuntu-26.04
permissions:
contents: read
id-token: write
steps:
- uses: actions/checkout@v4
- uses: starslingdev/disks@v0
with:
preset: pnpm-store
publish: ${{ github.ref == format('refs/heads/{0}', github.event.repository.default_branch) && 'on-change' || 'never' }}
- run: pnpm install --frozen-lockfile
- run: pnpm testThe first use of a new name mounts an empty directory. We call that a cold mount. The first successful publication populates the disk; later jobs mount its published generation warm. A step with publish omitted defaults to never and does not keep its writes for later jobs.
How jobs share a disk
A generation is the disk's single published state. Each job reads one fixed generation, selected at its first mount. Another job publishing while it runs does not change the files it sees. Disks currently allow one publisher at a time and many concurrent readers.
- Only a default-branch job can publish. Same-repository pull requests and jobs on other branches or tags can read, but cannot publish. A request to publish from those jobs is denied while the disk remains usable.
- One publisher at a time. If concurrent jobs ask to publish the same disk, only one gets the publisher lease. The others report
lease-heldand discard their writes at job end. Writes are never merged; serialize publishing jobs for a disk when every job's changes must land. - Publication waits for success. StarSling publishes after the job succeeds. Failed or cancelled jobs leave the shared generation unchanged. There is no save step, and a publication failure does not change the job's result.
Fork pull requests, Dependabot pull requests, and events outside the read allow-list receive no mount. A job without id-token: write also runs without its disks. See the action's error codes and failure handling for how to handle these cases.
Let us know if you need hundreds of CI jobs to write to distinct files in the same generation concurrently.
Auto-mount and paths
Before the steps of a trusted job with id-token: write, the runner mounts published disks under the job user's home whose last publication allowed auto-mount. These mounts are read-write; writes remain private and are discarded unless a step asks to publish them. The job log's StarSling group lists each disk's path, access, and generation, or the reason it did not mount.
Workspace disks and disks published with auto-mount: false always need an explicit action step. If job verification is still in progress before the steps run, home-directory disks also need the step for reads; the job log reports that case.
The action resolves the mount path in this order:
- The step's explicit
path. - The disk's stored path.
- The preset's default path.
~/.disks/<name>when no stored path or preset supplies one.
A leading ~/ resolves under the job user's home; a relative path resolves under GITHUB_WORKSPACE. Targets must be empty and within the job home or workspace, and cannot overlap another disk. The workspace root itself, Docker's data root, and container jobs are outside the action's scope.
Resolved paths must contain only printable ASCII characters without spaces. Paths containing whitespace or non-ASCII characters, and ~user paths, fail with invalid-input even when on-degraded: continue is set.
If the disk is already mounted, the step can attach a publish intent to it. Earlier writes in the same job count toward publication too. A repeat asking for a different path or access fails with conflicting-repeat; it cannot turn a pre-job read-write mount into a read-only one.
Build output in the workspace
Mount workspace disks after checkout and before the build tool writes to the directory. For example, a Rust job can keep target/ across runs. This step belongs in a job with the same runner and permissions as the quickstart:
- uses: actions/checkout@v4
- uses: starslingdev/disks@v0
with:
name: rust-target
path: target
publish: ${{ github.ref == format('refs/heads/{0}', github.event.repository.default_branch) && 'on-change' || 'never' }}
- run: cargo test --lockedFor dependencies, the cargo preset mounts the registry, rather than all of ~/.cargo, where the runner keeps rustup's binaries. Leave CARGO_HOME unset. Cargo Git dependencies need a separate named disk; build output uses a workspace path as above.
Read-only reference data
Use access: read-only with publish: never for datasets or fixtures the job must not rewrite. If the disk lives under the home directory, its publisher must store auto-mount: false so the reader can request read-only access before it is mounted. on-degraded: fail stops the step if that access cannot be supplied.
A cold mount is still a successful mount: strict handling does not prove that reference data exists. Check the mount output or validate the expected files when the job requires an existing generation.
Publish modes
Every publishing mode waits until the job succeeds and requires the job to be the disk's publisher.
publish | When writes become a new generation |
|---|---|
never | Never; changes stay private to this job and are discarded. |
on-change | The change test detects a change, or the step's settings differ from the stored ones. |
always | After success, even if nothing changed. |
if-missing | Only when no usable generation exists. An existing usable generation reports not-needed. |
The change test is what publish: on-change counts as a change. content-only counts file contents, types, owners, modes, and links. content-and-mtime also counts modification times, for tools that read them as freshness. The preset supplies the change test; the action has no separate change-test input.
The last successful default-branch publication stores the preset, size cap, change test, and auto-mount setting. Omitted settings return to their defaults: an omitted size-cap uses the preset's or API default, rather than keeping a previously customized cap.
Disk lifetime and changes
A disk retires after a publication over its size cap, after 30 days without any step naming it, or when a new name's first publication takes its path. A retired disk mounts cold; its next publication starts again from that job's writes. Rewriting files can still grow the disk toward its cap.
To move a published disk, use a new name. Publishing the old name at a different path reports settings-mismatch. To replace a name at the same path, first publish the old name with auto-mount: false, then publish the new name there; that first publication retires the old disk. A reader can choose another path for one job only if the runner has not already mounted the disk.
Presets and limits
List StarSling Disks presets returns every preset a step may name in preset, plus the limits every disk is held to. The catalog is the same for every caller.
List an org's StarSling Disks returns the disks an organization's repositories keep. Each entry includes the mount path, what the disk currently holds, and when that was published. Pass repository_id to limit the list to one repository. The caller must be a member of the org.
Security and deletion
StarSling Disks encrypts file contents and filenames at rest using a unique encryption key for each immutable storage layer. Deleted Disks data is removed within 48 hours, and account deletion includes stored Disks.
See Data Handling for key scope, retention, and the deletion process. The 48-hour billing delay below is separate from the data-deletion guarantee.
Price
Storage is $0.50 per GB-month, with no per-job attach fee. We calculate usage by snapshotting your total storage once a day, so billed usage reflects changes in the amount you store over the month. When you delete data, you may still be billed for that storage for up to 48 hours.
Organizations on the free trial do not get Disks storage. See Pricing.