Files
computer-playbook/docs/contributing/artefact/image/origin.md
T
kevinveenbirkenbachandClaude Opus 4.7 16cab61397 fix(image-lookup): require explicit role_id to prevent wrong-role resolution
The `image` lookup inferred `role_id` from `role_name` when only
`service_name` was passed. Inference broke lazy re-evaluation: a default
like `HEALTH_CSP_IMAGE: "{{ lookup('image', 'csp-checker', 'ref') }}"`
defined in sys-ctl-hlth-csp got re-evaluated inside sys-service's
systemctl.service.j2 template (via `system_service_tpl_exec_start`),
where `role_name='sys-service'`. The lookup then searched
`roles/sys-service/meta/services.yml` for `csp-checker` and aborted with
`no image mapping found for role 'sys-service' service 'csp-checker'`.

CI run 25976825584 surfaced this on six apps (web-app-{bridgy-fed,chess,
mig,hugo,mini-qr,postmarks}) whose deploy reached sys-svc-webserver-core
outside the service-loader preload context, which is what actually
triggers the sys-ctl-hlth-csp -> sys-service include chain.

Make `role_id` mandatory in the plugin signature, remove `_infer_role_id`,
and update the three callsites that relied on inference
(sys-ctl-hlth-csp, sys-svc-container, test-e2e-playwright) to pass the
owning role id explicitly. A migration guard catches the removed
`(service_name, want)` form and emits a clear error instead of the
confusing "missing services.yml" file-not-found message.

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

3.6 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:
    inter: 8080         # internal container port (per req-009)
  # … 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('image', ...) instead of reading the meta/services.yml keys directly. The owning role_id MUST be passed explicitly as the first positional argument. Calls without an explicit role_id MUST raise an error so a lazily re-evaluated expression rendered in another role's template context cannot silently resolve the wrong role.

Supported form:

{{ lookup('image', '<role_id>', '<service_name>'[, '<want>']) }}

Examples:

{{ lookup('image', 'test-e2e-playwright', 'playwright', 'image') }}
{{ lookup('image', 'sys-ctl-hlth-csp', 'csp-checker', 'ref') }}

Supported want values:

Value Returns
all (default) the merged mapping
image the repository/image name
version the tag
ref image:version

Override ✏️

Inventory-side mirror and manual overrides MUST be written to images_overrides.<role>.<service>.{image,version}. lookup('image', ...) MUST prefer images_overrides and fall back field-wise to the role's meta/services.yml entry. Inventory generation MUST keep images_overrides separate from meta/services.yml so role defaults remain readable and stable.

Example host-vars override:

images_overrides:
  test-e2e-playwright:
    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.