- Move iter_role_images + all helpers + ImageRef from cli/mirror/util.py and
cli/mirror/model.py into utils/docker/image_discovery.py; add source_file field
to ImageRef to distinguish config/main.yml vs vars/main.yml entries
- Delete cli/mirror/util.py; update all callers to import from utils.docker.image_discovery
- Move utils into subpackages: utils/docker/ (image_ref, paths_utils, hadolint) and
utils/roles/ (dependency_resolver, resource_validation); update all imports
- Reorganise tests into subpackages: tests/lint/docker/dockerfile/, tests/integration/docker/,
tests/unit/utils/docker/, tests/unit/utils/roles/; fix parents[N] offsets after relocation
- tests/lint/docker/test_image_versions.py now uses iter_role_images (same SPOT as mirror)
- Add vars/main.yml images.<name>.{image,version} support for non-application roles
(test-e2e-playwright, sys-ctl-hlth-csp); eliminate hardcoded duplicates in defaults/tasks
- Add web-app-matrix matrix-chatgpt-bot and web-app-mini-qr mini-qr entries in config/main.yml;
replace hardcoded image strings in vars/templates with lookup('config', ...) references
- Fix sync/__main__.py log line to use correct registry prefix for non-Docker-Hub images
- Update docs/contributing/code/role/dockerfile.md with image declaration rules and SPOT reference
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
4.5 KiB
Dockerfile
This page is the SPOT for role-local Dockerfiles. Use this page for placement rules, variable handling, and build wiring. For the agent-side review workflow during development, see Development.
Placement
- You MUST place role-local Dockerfiles at
files/Dockerfile. - You MUST NOT use
templates/Dockerfile.j2unless the Dockerfile requires Jinja2 control-flow logic (e.g.{% if %},{% for %}). sys-svc-composediscovers the Dockerfile automatically by checkingtemplates/Dockerfile.j2first and thenfiles/Dockerfile. Both are rendered through the Ansibletemplatemodule.
Variables
- You MUST NOT hard-code values that come from
config/main.ymlorvars/main.ymldirectly infiles/Dockerfile. - You MUST declare each external value as a Docker
ARGwithout a default value so the build always requires the value to be passed explicitly. - You MUST pass every
ARGvia theargs:block intemplates/compose.yml.j2, directly after the{{ lookup('template', 'roles/sys-svc-container/templates/build.yml.j2') }}call. - The
vars/main.ymlof the role MUST define the variables referenced inargs:by reading them fromconfig/main.ymlthrough thelookup('config', ...)filter. This keepsconfig/main.ymlas the single source of truth.
Example files/Dockerfile:
ARG APP_IMAGE
ARG APP_VERSION
FROM ${APP_IMAGE}:${APP_VERSION}
Example templates/compose.yml.j2 wiring:
{{ lookup('template', 'roles/sys-svc-container/templates/build.yml.j2') | indent(4) }}
args:
APP_IMAGE: "{{ APP_IMAGE }}"
APP_VERSION: "{{ APP_VERSION }}"
Example config/main.yml entry:
compose:
services:
myapp:
image: myapp/myapp
version: "1.0"
Example vars/main.yml entry:
APP_IMAGE: "{{ lookup('config', application_id, 'compose.services.myapp.image') }}"
APP_VERSION: "{{ lookup('config', application_id, 'compose.services.myapp.version') }}"
Image Declaration
Every Docker image used in a role must be declared in exactly one place — no hardcoded image strings anywhere else (tasks, templates, defaults).
Application roles (have application_id)
Declare the image under config/main.yml → compose.services.<service>.{image,version}:
compose:
services:
myapp:
image: ghcr.io/vendor/myapp
version: "1.0"
Any Ansible variable that references the image MUST read from config via lookup('config', ...):
# vars/main.yml
MY_APP_IMAGE: "{{ lookup('config', application_id, 'compose.services.myapp.image') }}"
MY_APP_VERSION: "{{ lookup('config', application_id, 'compose.services.myapp.version') }}"
Non-application roles
For roles without application_id that need extra images mirrored (e.g. test runners,
health checkers), declare them under vars/main.yml → images.<name>.{image,version}:
# vars/main.yml
images:
myimage:
image: mcr.microsoft.com/vendor/myimage
version: "v1.2.3"
Any variable that references the image MUST read from this block:
MY_IMAGE: "{{ images['myimage']['image'] }}:{{ images['myimage']['version'] }}"
Image discovery SPOT
utils/docker/image_discovery.py is
the single SPOT that enumerates all role images from both sources above.
It is used by the mirror pipeline (cli/mirror/) and the version-check lint test
(tests/lint/docker/test_image_versions.py).
When Dockerfile.j2 is acceptable
A templates/Dockerfile.j2 is acceptable when the file contains Jinja2
control-flow logic that cannot be expressed through Docker ARG alone — for
example, a conditional build step that installs an optional component only when
a feature flag is enabled.
Even in that case, you SHOULD minimize the templated surface: use {{ variables }}
only where necessary and keep the static parts of the Dockerfile readable without
rendering it.
Lint
The repository lint suite checks for templates/Dockerfile.j2 files automatically:
- A
Dockerfile.j2with no Jinja2 control-flow logic causes a test failure. It MUST be migrated tofiles/DockerfilewithARGdeclarations. - A
Dockerfile.j2with Jinja2 control-flow logic emits a warning only. The warning signals that the file should be reviewed to see whether the logic can be eliminated and the file migrated.
See test_templates.py for the implementation.