Pull from GitHub Container Registry (GHCR)
GHCR (ghcr.io) is a container registry hosted by GitHub. Public images
work with no setup — Rune pulls them like any other registry. Private
images need credentials: a GitHub token with read:packages, registered
with runed so the Docker runner can use it on every pull.
This guide covers the two supported flows:
- Direct (recommended) — paste a GitHub PAT into a registry entry.
- From an existing
docker login— reuse~/.docker/config.json.
Both flows result in ghcr.io images pulling without ImageUnreachable
on rune cast.
1. Mint a GitHub token
Section titled “1. Mint a GitHub token”Generate a Personal Access Token at
https://github.com/settings/tokens with the read:packages scope:
- Classic PAT: check
read:packages. (repois not required for pulling.) - Fine-grained PAT: under Permissions → Account permissions set Packages to Read-only.
For organization-owned packages, also make sure the token’s user has
read access to the package on the Packages → Package settings →
Manage Actions access page. A token can have the right scope but still
get denied: permission_denied if the package itself doesn’t grant the
user access.
Test the token from a shell:
echo "$GHCR_PAT" | docker login ghcr.io -u <github-username> --password-stdindocker pull ghcr.io/<org>/<image>:<tag>If docker pull works, Rune will work — the same daemon performs both
pulls.
2a. Direct: register the token with runed
Section titled “2a. Direct: register the token with runed”There are two equivalent ways to add a registry entry: at runtime via
rune admin registry add, or statically in the runefile.
Runtime (no restart)
Section titled “Runtime (no restart)”Run from the same host as runed (admin commands are localhost-only by
default):
rune admin registry add \ --name ghcr \ --registry ghcr.io \ --type basic \ --username "$GITHUB_USER" \ --password "$GHCR_PAT"Verify:
rune admin registry listrune admin registry test ghcrtest performs a real auth handshake and returns ok or the registry
error message verbatim.
Static (in the runefile)
Section titled “Static (in the runefile)”docker: registries: - name: ghcr registry: ghcr.io auth: type: basic username: ${GHCR_USER} # env-expanded at runed start password: ${GHCR_PAT}runed reads its config at startup, so changes here require a restart.
Prefer the rune admin registry route once the cluster is running.
2b. Reuse an existing docker login
Section titled “2b. Reuse an existing docker login”If the host already has working docker login ghcr.io, you can hand the
contents of ~/.docker/config.json to Rune verbatim instead of
re-typing the credentials:
rune admin registry add \ --name ghcr-from-docker \ --registry ghcr.io \ --type dockerconfigjson \ --data dockerconfigjson="$(cat ~/.docker/config.json)"This is useful when:
- You manage GHCR auth via a CI step that runs
docker loginfor you (e.g.docker/login-actionon a self-hosted runner). - You want to ship one credential blob that covers multiple registries in a single file.
3. Cast a private image
Section titled “3. Cast a private image”Once the registry is registered, no service-spec change is needed —
image: references just work:
service: name: api namespace: default image: ghcr.io/myorg/api:1.4.0 scale: 1rune cast api.yamlIf credentials are missing or wrong you will see the dev.27 inline failure block:
✗ ImageUnreachable pull access denied for ghcr.io/myorg/api, repository does not exist or may require 'docker login'
image: ghcr.io/myorg/api:1.4.0 service: default/api
rune logs api -n default --tail=50That error text is the registry’s response, surfaced directly.
4. Rotate or revoke
Section titled “4. Rotate or revoke”Rotate by issuing a new PAT, then:
rune admin registry update \ --name ghcr \ --registry ghcr.io \ --type basic \ --username "$GITHUB_USER" \ --password "$NEW_PAT"Revoke at https://github.com/settings/tokens. Existing pulls in flight keep their credentials for the lifetime of that pull; new pulls fail immediately.
To remove a registry entirely:
rune admin registry remove ghcrPer-service overrides
Section titled “Per-service overrides”A service can pin to a specific named registry or supply inline credentials, overriding the host-level match:
service: name: api image: ghcr.io/myorg/api:1.4.0 registryAuth: name: ghcr # use this named registry instead of host-matchingInline auth is also supported but discouraged outside of one-off
debugging — secrets land in the service spec instead of runed’s
config.
Troubleshooting
Section titled “Troubleshooting”| Symptom | Cause | Fix |
|---|---|---|
ImageUnreachable with pull access denied | Token missing read:packages or package doesn’t grant the user access. | Regenerate PAT with the right scope; check Packages → Manage Actions access. |
unauthorized: authentication required | No registry entry matches ghcr.io. | rune admin registry list — add one if missing. |
manifest unknown | Image exists but tag doesn’t, or org/name is wrong. | docker pull from the same host to confirm. |
| Works locally, fails on the server | Runed wasn’t restarted after a static config change, or the env var ($GHCR_PAT) isn’t visible to runed. | Use rune admin registry add instead of editing the runefile, or restart runed with the env exported. |
See also
Section titled “See also”rune admin registry— full subcommand reference.- Configuration reference — full runefile
schema, including the
docker.registries[]section. - Errors: ImageUnreachable — what the reason slug means.