- Move image_discovery.py → utils/docker/image/discovery.py - Move image_ref.py → utils/docker/image/ref.py - Add utils/docker/image/__init__.py - Update all imports and references across cli/, tests/, utils/, and docs/ Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
4.3 KiB
Role Image Configuration 🐳
This document is the SPOT for how Docker images are declared, read, overridden, discovered, and mirrored. For the mirroring pipeline, see mirror.md.
Declaration Formats 📋
A role MAY declare images in two ways depending on how the image is used.
Compose Service Format — config/main.yml
Used for images that are started as part of the role's Docker Compose stack:
# roles/<role>/config/main.yml
compose:
services:
<service-name>:
image: <image-name> # e.g. nextcloud, quay.io/keycloak/keycloak
version: <tag> # e.g. latest, 31.0.0
port: 8080
# … other compose fields
Flat Image Format — defaults/main.yml
Used for images referenced directly in Ansible tasks or templates rather than
through Compose (e.g. images pulled and run ad-hoc by a task). The role name is
implicit from the file path roles/<role>/defaults/main.yml.
# roles/<role>/defaults/main.yml
images:
<service-name>:
image: <image-name> # e.g. mcr.microsoft.com/playwright, postgres
version: <tag> # e.g. v1.58.2-noble
imageMUST be the image name as it appears in adocker pullcommand, without the tag.versionMUST be the tag.- Images without an explicit registry prefix are treated as Docker Hub images.
- Role-local image defaults MUST stay in
images. Inventory-side overrides MUST NOT be written there.
Why defaults/ and not vars/? 🤔
The images: block holds role-local defaults, not inventory state. Keeping it
in defaults/main.yml makes that intent explicit and keeps the role-side
declaration separate from inventory-side images_overrides. Using vars/ would
give those defaults higher precedence and blur the separation.
Read 📖
Roles MUST use lookup('image', ...) instead of direct images[...] access.
The lookup MAY infer the current role id from role_name. If role_name is not
available, the role id MUST be passed explicitly.
Supported forms:
{{ lookup('image', 'playwright', 'image') }}
{{ 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 role-local images.
Inventory generation MUST keep images_overrides separate from images 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 all roles and yields ImageRef objects:
| Source file | Key path | Used for |
|---|---|---|
config/main.yml |
compose.services.<svc>.image + .version |
Compose-managed services |
defaults/main.yml |
images.<svc>.image + .version |
Ad-hoc task images |
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.
config/main.ymldeclarations are resolved back throughmirrors.yml.applications.<role>.compose.services.<service>defaults/main.ymldeclarations are resolved back throughmirrors.yml.images.<role>.<service>and land in host vars underimages_overrides.<role>.<service>
See mirror.md for the full flow.