Skip to content

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:

  1. Direct (recommended) — paste a GitHub PAT into a registry entry.
  2. From an existing docker login — reuse ~/.docker/config.json.

Both flows result in ghcr.io images pulling without ImageUnreachable on rune cast.

Generate a Personal Access Token at https://github.com/settings/tokens with the read:packages scope:

  • Classic PAT: check read:packages. (repo is 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:

Terminal window
echo "$GHCR_PAT" | docker login ghcr.io -u <github-username> --password-stdin
docker pull ghcr.io/<org>/<image>:<tag>

If docker pull works, Rune will work — the same daemon performs both pulls.

There are two equivalent ways to add a registry entry: at runtime via rune admin registry add, or statically in the runefile.

Run from the same host as runed (admin commands are localhost-only by default):

Terminal window
rune admin registry add \
--name ghcr \
--registry ghcr.io \
--type basic \
--username "$GITHUB_USER" \
--password "$GHCR_PAT"

Verify:

Terminal window
rune admin registry list
rune admin registry test ghcr

test performs a real auth handshake and returns ok or the registry error message verbatim.

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.

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:

Terminal window
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 login for you (e.g. docker/login-action on a self-hosted runner).
  • You want to ship one credential blob that covers multiple registries in a single file.

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: 1
Terminal window
rune cast api.yaml

If 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=50

That error text is the registry’s response, surfaced directly.

Rotate by issuing a new PAT, then:

Terminal window
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:

Terminal window
rune admin registry remove ghcr

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-matching

Inline auth is also supported but discouraged outside of one-off debugging — secrets land in the service spec instead of runed’s config.

SymptomCauseFix
ImageUnreachable with pull access deniedToken missing read:packages or package doesn’t grant the user access.Regenerate PAT with the right scope; check Packages → Manage Actions access.
unauthorized: authentication requiredNo registry entry matches ghcr.io.rune admin registry list — add one if missing.
manifest unknownImage exists but tag doesn’t, or org/name is wrong.docker pull from the same host to confirm.
Works locally, fails on the serverRuned 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.