Indices
OCI tags are mutable. The OCI Distribution Specification defines tags as registry-side aliases that can be re-pointed at any moment — cmake:3 today may resolve to a different digest after the next patch release. For a tool that installs binaries reproducibly, this is a problem: ocx install cmake:3 run twice on different days could silently resolve different builds.
OCX solves this by keeping a local snapshot of tag-to-digest mappings. That snapshot only changes when explicitly refreshed. The same snapshot always resolves cmake:3 to the same digest — regardless of what the registry serves today. This page explains how the three index modes (local, remote, active) interact, what the snapshot bundle pattern enables for Actions/Bazel/DevContainer authors, and why OCX never auto-updates the local index. The user-facing surface — when to refresh, offline mode, --remote — lives in the Indices section of the user guide.
Remote Index
The remote index is the live OCI registry. It answers metadata queries — which tags exist for a package, which digest a tag currently points to, which platforms a manifest declares — directly and authoritatively.
ocx index catalog browses available packages; ocx index list lists tags for a specific package. Both query the live registry when the global flag --remote is set.
The remote index is also the data source for ocx index update. Refreshing the local snapshot means querying the remote index and writing the results to disk.
Local Index
The local index reads from ~/.ocx/tags/ — a snapshot of OCI tag-to-digest mappings. Resolving cmake:3 is a file read; no network request is made.
The local index is never updated automatically. You decide when your snapshot changes. Until you explicitly refresh it, the same identifier always resolves to the same digest — on your laptop, on CI, and on every team member's machine. Rolling tags like cmake:3 map to the digest current at last update, not whatever the registry serves today.
Similar to APT's package lists
apt-get update downloads package metadata from configured sources and caches it in /var/lib/apt/lists/. All subsequent apt-get install calls resolve packages from that local snapshot — the network is only involved during an explicit refresh, not on every install. ocx index update <package> is the per-package equivalent: you control when the snapshot changes, and the rest of the time you work from the local cache.
Update modes
ocx index update <package> syncs the local index for a specific package from the remote registry:
- Bare identifier (e.g.,
cmake) — downloads all tag-to-digest mappings for the repository. - Tagged identifier (e.g.,
cmake:3.28) — fetches only that single tag's digest and manifest, merging it into the existing local tags file.
Tag-scoped mode is ideal for lockfile workflows where the index should contain only explicitly requested tags. Packages not listed are not touched.
Fresh-machine fallback
On a fresh machine, ocx index update does not need to run before the first ocx install cmake:3.28. When the local tag store has no entry for a requested tag, ocx install transparently resolves that single tag against the configured remote, persists it to the tag store, and proceeds with the install. Subsequent commands — including --offline — then work from the cached entry without touching the network.
Refreshing a cached tag or discovering every tag for a repository is still the job of ocx index update; the fallback only covers the specific tag being installed.
Active Index
Every command that resolves a package identifier — ocx install, ocx find, ocx exec, ocx index list — uses one working index for that invocation. By default, this is the local index. Two flags change which index is used:
| Mode | Flag | Source | Network? |
|---|---|---|---|
| Default | (none) | Local snapshot | No (unless fetching a new binary) |
| Remote | --remote | OCI registry | Yes |
| Offline | --offline | Local snapshot | Never |
--remote forces tag and catalog lookups to query the registry directly for a single command. The persistent local tag store ($OCX_HOME/tags/) is not updated. Blob data fetched under --remote still writes through to $OCX_HOME/blobs/, so the command populates the blob cache while bypassing the tag snapshot. Use it for a one-off check — seeing currently available tags, or resolving the latest digest — without committing the tag resolution result to the local snapshot.
--offline prevents all network access for that command. If the local index does not have a requested package, the command fails immediately rather than attempting a registry query. Useful to verify that the current index and object store are self-sufficient before a build in a restricted or air-gapped environment.
--index / OCX_INDEX do not change the active index mode — the local snapshot remains active. They only change where that snapshot is read from. See Bundled Snapshots.
The active index controls tag and manifest resolution only. The package store is independent — installed binaries are accessible in all three modes regardless of which index is active.
Bundled Snapshots
The local snapshot is small enough — JSON metadata only, no binaries — to ship inside a tool release. Bazel Rules, GitHub Actions, and DevContainer Features can bundle a frozen snapshot at release time and set OCX_INDEX (or pass --index) to point OCX at it. Consumers write cmake:3 and the bundled snapshot resolves it deterministically, with the package store and install symlinks remaining in OCX_HOME as usual.
This produces a two-level lock: the tool version locks the bundled snapshot, which locks the resolved binary. A version bump to the action or rule — proposed automatically by Dependabot or Renovate — advances the bundled index. Users get the updated binary with no config changes.
GitHub Action with bundled index
- uses: ocx-actions/setup-cmake@v2.1.0 # pins action → pins index → pins binary
with:
version: "3.28" # human-readable tag, no platform conditions@v2.1.0 locks everything end-to-end. @v2 follows minor releases — as the maintainer ships updated index snapshots, cmake:3.28 may resolve to a newer build when the action version changes. No SHA256 lists, no if: runner.os == 'Linux' conditionals.
The contrast with maintaining a hand-curated URL matrix — one filename → checksum entry per version × os × arch — is clear: a version bump means editing one rule version, not a dictionary.
See Also
- Indices section in the user guide — how-to: refresh, work offline, use
--remote - Storage —
~/.ocx/tags/layout, tag-store as a store - Versioning — tag mutability, locking by digest,
_build suffix - Configuration — config-driven defaults that pair with bundled snapshots