Files
computer-playbook/docs/contributing/code/role/dockerfile.md
T
Environment TestandClaude Sonnet 4.6 aa33256cde feat(mirror): split resolver output, move images to defaults, add lookup plugin
- resolver: output both applications: (config/main.yml compose images) and
  images: (defaults/main.yml images) keyed by source_file
- image_discovery: scan defaults/main.yml instead of vars/main.yml for images:
- mirror_overrides: write mirror URLs to applications.*.compose.services.*
  (mirror_policy-aware) and images_overrides.*.* (always merged, if_missing)
- roles/sys-ctl-hlth-csp, roles/test-e2e-playwright: move images: from
  vars/main.yml to defaults/main.yml for correct Ansible variable precedence
- plugins/lookup/image.py: new Ansible lookup plugin that prefers
  images_overrides over role-local images defaults
- docs: add artefact/image.md and artefact/mirror.md as SPOTs; add
  code/role/image.md contributor guide; link from ci.md
- tests: update test_util, test_mirror_overrides; add test_resolver,
  test_publish (404/401 coverage), test_image (lookup plugin)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 11:17:42 +02:00

124 lines
4.4 KiB
Markdown

# `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](../../../agents/action/develop.md).
## Placement
- You MUST place role-local Dockerfiles at `files/Dockerfile`.
- You MUST NOT use `templates/Dockerfile.j2` unless the Dockerfile requires Jinja2
control-flow logic (e.g. `{% if %}`, `{% for %}`).
- `sys-svc-compose` discovers the Dockerfile automatically by checking
`templates/Dockerfile.j2` first and then `files/Dockerfile`.
Both are rendered through the Ansible `template` module.
## Variables
- You MUST NOT hard-code values that come from `config/main.yml` or `vars/main.yml`
directly in `files/Dockerfile`.
- You MUST declare each external value as a Docker `ARG` without a default value
so the build always requires the value to be passed explicitly.
- You MUST pass every `ARG` via the `args:` block in `templates/compose.yml.j2`,
directly after the `{{ lookup('template', 'roles/sys-svc-container/templates/build.yml.j2') }}` call.
- The `vars/main.yml` of the role MUST define the variables referenced in `args:`
by reading them from `config/main.yml` through the `lookup('config', ...)` filter.
This keeps `config/main.yml` as the single source of truth.
Example `files/Dockerfile`:
```dockerfile
ARG APP_IMAGE
ARG APP_VERSION
FROM ${APP_IMAGE}:${APP_VERSION}
```
Example `templates/compose.yml.j2` wiring:
```yaml
{{ 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:
```yaml
compose:
services:
myapp:
image: myapp/myapp
version: "1.0"
```
Example `vars/main.yml` entry:
```yaml
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}`:
```yaml
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', ...)`:
```yaml
# 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 mirrored images (e.g. test runners,
health checkers), use [Contributing `image`](image.md) as the SPOT for declaration and access.
Those images MUST be declared under `defaults/main.yml` and MUST be consumed via
`lookup('image', ...)` instead of direct `images[...]` access.
### Image discovery SPOT
[`utils/docker/image_discovery.py`](../../../../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.j2` with **no Jinja2 control-flow logic** causes a **test failure**.
It MUST be migrated to `files/Dockerfile` with `ARG` declarations.
- A `Dockerfile.j2` with **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](../../../../tests/lint/docker/dockerfile/test_templates.py)
for the implementation.