image_caches
Each build target pulls a container image. Image caches store prepared images (the distro base plus everything setup installs) in a registry or locally, so later builds start from the snapshot and skip setup entirely.
The cache is populated by prime and consumed by build / publish / release via --image-cache <name>. If the named cache is configured but not yet primed, the container runtime fails to pull it — run prime first.
Providers
registry
Push cached images to any OCI-compatible container registry (GHCR, Docker Hub, GitLab Registry, self-hosted Harbor, …).
image_caches:
- name: my-cache
provider: registry
image_tag: omnipackage-cache
registry:
url: ghcr.io
namespace: myorg
username: ${REGISTRY_USER}
password: ${REGISTRY_TOKEN}
The full image reference is <url>/<namespace>/<distro_id>:<image_tag>, e.g. ghcr.io/myorg/fedora_42:omnipackage-cache. One image per distro, with image_tag shared across all of them.
local
Snapshot the image into the container runtime's local store. No registry, no push — for quick local iteration when you just want to skip setup on subsequent runs.
GitHub Container Registry (ghcr.io)
GHCR is the most common CI target (free, integrated with GitHub Actions) and the most common source of credential confusion. Two cases:
From GitHub Actions: GITHUB_TOKEN
Inside a workflow, the auto-injected GITHUB_TOKEN can push and pull from GHCR — no PAT required. The job must declare write access:
(The default for modern repos is read-only — enough to pull but not push; prime fails without write.) In the workflow step, pass the values into .env so ${...} substitution in config.yml can read them:
- run: |
echo "GITHUB_REGISTRY_USERNAME=${{ github.actor }}" >> .env
echo "GITHUB_REGISTRY_PASSWORD=${{ secrets.GITHUB_TOKEN }}" >> .env
echo "GITHUB_REGISTRY_NAMESPACE=${{ github.repository_owner }}" >> .env
GITHUB_TOKEN is ephemeral — scoped to the workflow run, no rotation needed.
Outside Actions (local dev, other CI): Personal Access Token
GITHUB_TOKEN only exists inside a GitHub Actions runner. To prime from your laptop or other CI, use a classic Personal Access Token with write:packages and read:packages scopes (fine-grained tokens do not currently cover GHCR — use classic). Create it at GitHub → Settings → Developer settings → Personal access tokens → Tokens (classic), then paste it into your local .env:
GITHUB_REGISTRY_USERNAME=your-github-username
GITHUB_REGISTRY_PASSWORD=ghp_xxxxxxxxxxxxxxxxxxxx
GITHUB_REGISTRY_NAMESPACE=your-github-username # or an org you have package-write on
The same env-var names work in both cases — only the source of the token differs.
namespace: org vs personal
GHCR images are owned by either a GitHub user or an organization. namespace is whichever one owns the package:
- Personal repo or personal-account
prime:namespaceis your GitHub username (${{ github.repository_owner }}resolves to this in personal-repo workflows). - Org-owned repo:
namespaceis the org name. The token (GITHUB_TOKENor PAT) needspackages: writeon the org's packages.
For projects primed from both org-owned CI and contributor forks, declare two image_caches: entries, one per namespace, and select with --image-cache <name> at run time. omnipackage-rs/.omnipackage/config.yml does this with github and github_personal entries.
Real-world references
mpz— singlegithubentry, primed monthly from GitHub Actions withGITHUB_TOKEN.omnipackage-rs— dualgithub/github_personalentries for org + contributor-fork workflows.
The matching CI wiring (refresh-omnipackage-cache.yml, permissions: packages: write, the .env-writing step) is in the CI/CD integration guide.