Caching your builds

Build in CI, push only your novel paths to Vega, and pull them back on your machines.

Vega's tenant tier is a per-namespace cache: a verified CI build publishes immediately under tenant/<owner>/<repo>, signed with a key derived for that tenant. No cross-party agreement is involved (that is the shared tier); this is the path for caching your own packages or NixOS closures.

What the vega agent is

The vega-agent repository ships three programs. You normally touch only the first; the others run for you in CI.

Consuming the cache needs none of these: just add Vega as a substituter (see Configuration). The agent is only for publishing your own builds and verifying.

Get the vega CLI

Source on GitHub ↗

Three ways to get it, depending on whether you want vega permanently on your PATH. With (1) every call needs the full nix run prefix; with (2) and (3) you call vega directly.

1. Run it ad hoc, without installing anything:

nix run github:Ad-Astra-Computing/vega-agent#vega -- login
nix run github:Ad-Astra-Computing/vega-agent#vega -- verify /nix/store/<hash>-name

2. Install it into your profile, then call vega from anywhere:

nix profile add github:Ad-Astra-Computing/vega-agent#vega
vega login

3. Add it as a flake input, pinned to a release tag for repeatable builds. Put the package on your PATH (a devShell, environment.systemPackages, or home.packages) and call vega directly:

inputs.vega.url = "github:Ad-Astra-Computing/vega-agent/v0.13.0";
# optionally share this flake's nixpkgs:
#   inputs.vega.inputs.nixpkgs.follows = "nixpkgs";
# put vega.packages.<system>.default on PATH, then run vega.
Show a complete flake.nix
{
  inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
  inputs.vega.url = "github:Ad-Astra-Computing/vega-agent/v0.13.0";
  inputs.vega.inputs.nixpkgs.follows = "nixpkgs";

  outputs = { self, nixpkgs, vega, ... }:
    let
      system = "x86_64-linux";
      pkgs = nixpkgs.legacyPackages.${system};
    in {
      # vega on PATH in a dev shell
      devShells.${system}.default = pkgs.mkShell {
        packages = [ vega.packages.${system}.default ];
      };
      # or as a runnable app:  nix run .#vega -- verify ...
      apps.${system}.vega = vega.apps.${system}.vega;
    };
}

Declare what to cache: vega.yaml

Put a vega.yaml at your repository root listing the flake outputs to build and attest. The agent reads it and builds each one:

builds:
  - packages.x86_64-linux.mytool
  - attr: nixosConfigurations.myhost.config.system.build.toplevel
include:                # attribute-matcher globs: build everything matching
  - packages.x86_64-linux.*
exclude:                # ...minus these (applied last, to the whole set)
  - packages.x86_64-linux.wip
devShells:              # cache dev environments so contributors' nix develop substitutes
  - default
reuse-cache: false      # substitute this repo's own prior pushes before building
privacy:
  continent: true       # publish the builder's continent (continent only)

A bare string under builds is shorthand for { attr: <name> }. include/exclude are glob matchers over your flake's outputs (* matches one attribute component): build everything matching include, minus exclude, which is applied last to the whole set. devShells lists shells to cache as devShells.<system>.<name>, so a contributor's nix develop substitutes instead of rebuilding. reuse-cache registers your tenant cache as a substituter for faster cold builds; keep it off for any build whose attestation feeds the shared tier, since a build that substitutes from Vega is no longer independent of it. A present-but-invalid vega.yaml is a hard error, not a silent skip.

Continent publishing is on by default. With no vega.yaml, or one that does not set privacy.continent, each attestation records the builder's continent (continent granularity only, never city or IP), which feeds the aggregate "N continents" independence signal. To opt out, add a vega.yaml with privacy.continent: false; the builder is then recorded as unknown and never appears in any geographic aggregate.

Push from CI

The complete workflow. Copy it to .github/workflows/vega-cache.yml. It builds your flake on a GitHub-hosted runner and uploads and attests the result, authenticated by a GitHub Actions OIDC token (no stored secret):

# .github/workflows/vega-cache.yml
name: Cache with Vega

# Push and manual dispatch only, never pull_request: a fork PR can read the
# OIDC token, so the id-token permission must not exist on untrusted PR code.
on:
  push:
    branches: [main]
  workflow_dispatch:

permissions:
  contents: read
  id-token: write

concurrency:
  group: vega-cache-${{ github.ref }}
  cancel-in-progress: false

jobs:
  cache:
    runs-on: ubuntu-latest
    timeout-minutes: 60
    steps:
      - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
        with:
          persist-credentials: false
      - uses: Ad-Astra-Computing/vega-agent/agent@7c37e25ed66161c3711d05f2648ad93eb81aa3b7 # v0.13.0
        with:
          # github.workspace is your checkout root, so the attestation's
          # provenance matches the repo and the output can be reproduced. A flake
          # in a subdirectory works too: ${{ github.workspace }}/sub#attr is
          # recorded as ?dir=sub and reproduced from that subdirectory.
          installable: "${{ github.workspace }}#packages.x86_64-linux.default"
          control-plane: https://vega-cache.dev
          skip-upstream: "true"

Pin every action to a full commit SHA, not a tag, and let Dependabot bump them. A moved tag is the vector behind recent GitHub Actions supply-chain compromises, and a commit SHA is immutable, so a tag move cannot reach a SHA-pinned step. Change the installable attribute to what you want cached; with a vega.yaml in the repo you can omit it and the agent builds every declared output. The vega CLI writes this file for you: nix run github:Ad-Astra-Computing/vega-agent#vega -- init.

Install the Vega GitHub App

Install the Vega GitHub App on the repositories you cache. Attestation itself runs over your workflow's OIDC token (the permissions: id-token: write above) and does not require the App, but the App is what posts the Vega check on each attested commit, summarizing what was cached and linking to its /status page. It is the visible confirmation that a build was attested, so installing it is the recommended last step of setup. Anyone can also confirm a build directly with vega verify <hash> (see Transparency log).

Upload only novel paths

A NixOS closure is mostly stock nixpkgs that cache.nixos.org already serves. With skip-upstream: "true" the agent skips every closure path the upstream cache already has and uploads only your genuinely novel paths, so caching a system closure does not re-upload all of nixpkgs. Recommended for routine caching. Leave it off only when you deliberately want to attest the full closure for reproducibility.

Heavy or slow builds

A cold build of a large closure (a big Rust workspace, a full system) can take a long time. Vega imposes no build timeout of its own: your job's timeout-minutes is the only limit, so a long-but-progressing build is never cut short. Set build-timeout-minutes on the action only if you want an explicit per-build cap (for self-hosted runners or runaway builds).

To avoid rebuilding the same heavy paths every run, turn on reuse-cache so the agent substitutes your repo's prior pushes from Vega instead of rebuilding them. The first build still pays full cost; later runs pull it. Keep reuse-cache off for any job whose attestation feeds the shared tier, since a build that substitutes from Vega is no longer independent of it. If a dependency already has a trusted upstream cache (for example a project's Cachix), add it with extra-substituters and extra-trusted-public-keys so it is pulled rather than built from source.

Pull your builds back

Fetch your tenant's public key, then add the namespace as a substituter on each machine:

curl https://vega-cache.dev/tenant/<owner>/<repo>/key
extra-substituters = https://vega-cache.dev/tenant/<owner>/<repo>
extra-trusted-public-keys = vega-<owner>-<repo>-1:<from the endpoint above>
Keep cache.nixos.org as a substituter alongside Vega. Vega mirrors it, but routing all of nixpkgs through a single proxy adds latency and a dependency you do not need; add Vega for your own builds, not as a replacement for the upstream cache.

Limits

So one namespace cannot exhaust storage, owner pushes (vega push) are bounded: each uploaded NAR is capped (5 GiB by default) and each owner namespace has a storage quota (10 GiB by default). Both are operator-configurable, and a push that exceeds either is rejected. CI attestation through the GitHub Action is not subject to these push limits; its only time bound is your job's timeout-minutes (or an explicit build-timeout-minutes).