Files
computer-playbook/docs/contributing/artefact/image/origin.md
kevinveenbirkenbachandClaude Opus 4.7 b3c519b914 refactor(images): route role image refs through lookup('config', ...) so the GHCR mirror takes effect
`lookup('image', '<role>', '<svc>', 'ref')` only consulted
`images_overrides.*` plus role defaults, which the mirror pipeline
never populates — the pipeline writes into
`applications.<role>.services.<svc>.{image,version}`. As a result,
`sys-svc-container`'s `BUSYBOX_IMAGE` / `NODE_IMAGE`,
`sys-ctl-hlth-csp`'s `HEALTH_CSP_IMAGE`, and `test-e2e-playwright`'s
`TEST_E2E_PLAYWRIGHT_IMAGE` always resolved to the raw upstream ref
and hit Docker Hub directly. In CI this surfaced as
`toomanyrequests: ...unauthenticated pull rate limit` on
`busybox:1.37`.

Switches every productive `lookup('image', ...)` to
`lookup('config', '<role>', 'services.<svc>.image|version')` — the
same path templates like `web-app-oauth2-proxy` already use, which
resolves through the merged `applications.*` map and so transparently
picks up the GHCR mirror override written by the inventory creator.

Removes the now-unused `plugins/lookup/image.py` plugin and its unit
test (zero remaining consumers across roles, scripts, and tests).
Rewrites the image-origin / image-mirror / dockerfile docs to teach
the `lookup('config', ...)` pattern, dropping references to the
deleted plugin and the dead `images_overrides.*` variable. Doc edits
follow the `documentation.md` rules (no em dashes, one sentence per
line, no historical phrasing).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 02:53:05 +02:00

3.3 KiB

Role Image Configuration 🐳

This page describes how Docker images are declared, read, overridden, discovered, and mirrored. For the mirroring pipeline, see mirror.md.

Declaration Format 📋

Every role MUST declare its Docker images in meta/services.yml. The file root IS the services map keyed by <service-name> (no compose: and no services: wrapper):

# roles/<role>/meta/services.yml
<service-name>:
  image: <image-name>   # e.g. nextcloud, quay.io/keycloak/keycloak, mcr.microsoft.com/playwright
  version: <tag>        # e.g. latest, 31.0.0, v1.58.2-noble
  ports:
    internal:
      http: 8080        # internal container port (category-keyed; see layout.md)
  # … other compose fields
  • image MUST be the image name as it appears in a docker pull command, without the tag.
  • version MUST be the tag.
  • Images without an explicit registry prefix are treated as Docker Hub images.
  • Compose-managed services and ad-hoc task images both live under the same key on the role's primary or auxiliary service entries.
  • Role-local image defaults MUST stay in meta/services.yml.
  • Inventory-side overrides MUST NOT be written there.

Read 📖

Roles MUST use lookup('config', '<role_id>', 'services.<service_name>.<field>') to read image declarations. That path goes through the merged applications map, so it transparently picks up the mirror overrides written by the inventory creator (see mirror.md).

Pattern:

{{ lookup('config', '<role_id>', 'services.<service_name>.image') }}:{{ lookup('config', '<role_id>', 'services.<service_name>.version') }}

Examples:

{{ lookup('config', 'test-e2e-playwright', 'services.playwright.image') }}
{{ lookup('config', 'sys-ctl-hlth-csp', 'services.csp-checker.image') }}:{{ lookup('config', 'sys-ctl-hlth-csp', 'services.csp-checker.version') }}

Override ✏️

The override path is host-vars applications.<role>.services.<service>.{image,version}. The inventory mirror step (see mirror.md) writes there automatically; manual overrides go to the same key.

applications:
  test-e2e-playwright:
    services:
      playwright:
        image: ghcr.io/example/mirror/mcr.microsoft.com/playwright
        version: v1.58.2-noble

Supported Registries 🌐

The following registries are discovered and mirrored:

Registry Example image
docker.io (Docker Hub) postgres, nextcloud, prom/prometheus
quay.io quay.io/keycloak/keycloak
ghcr.io ghcr.io/mastodon/mastodon
mcr.microsoft.com mcr.microsoft.com/playwright

Images from other registries are ignored by the discovery and mirroring tooling.

Discovery 🔍

utils/docker/image/discovery.py scans every roles/<role>/meta/services.yml and yields ImageRef objects for each top-level service entry that carries both image and version.

An ImageRef carries: role, service, name (without registry), version, source (full pull ref), registry, and source_file.

Mirror Integration 🪞

After discovery, images are mirrored to GHCR and the mirror URLs are injected back into host variables by the inventory creator. Declarations are resolved back through mirrors.yml.applications.<role>.services.<service> and land in host vars at the same path.

See mirror.md for the full flow.