`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>
4.5 KiB
Dockerfile 🐳
This page covers 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
meta/services.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 frommeta/services.ymlthrough thelookup('config', ...)filter. This keepsmeta/services.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 meta/services.yml entry (file root IS the services map):
myapp:
image: myapp/myapp
version: "1.0"
Example vars/main.yml entry:
APP_IMAGE: "{{ lookup('config', application_id, 'services.myapp.image') }}"
APP_VERSION: "{{ lookup('config', application_id, 'services.myapp.version') }}"
Image Declaration 🐳
Every Docker image used in a role MUST be declared in exactly one place. No hardcoded image strings are allowed anywhere else (tasks, templates, defaults).
Application roles (have application_id) 📦
Declare the image at the file root of meta/services.yml → <service>.{image,version}:
# roles/<role>/meta/services.yml (file root IS the services map)
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, 'services.myapp.image') }}"
MY_APP_VERSION: "{{ lookup('config', application_id, 'services.myapp.version') }}"
Non-application roles 🔧
For roles without application_id that need extra mirrored images (e.g. test runners, health checkers), follow the rules in origin.md for declaration and access.
Those images MUST be declared under meta/services.yml and MUST be consumed via lookup('config', '<role_id>', 'services.<service>.image') (and .version) instead of direct images[...] access.
Image Discovery 🔍
image_discovery.py enumerates all role images from both sources above.
It is used by the mirror pipeline (cli/contributing/mirror/) and the external version-check test
(tests/external/update/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.