Skip to content

ci: single gate, merged image job, shared composite actions, docs workflow merge - #4221

Merged
aheritier merged 8 commits into
mainfrom
ci-workflow-cleanup
Sep 10, 2026
Merged

ci: single gate, merged image job, shared composite actions, docs workflow merge#4221
aheritier merged 8 commits into
mainfrom
ci-workflow-cleanup

Conversation

@aheritier

@aheritier aheritier commented Sep 10, 2026

Copy link
Copy Markdown
Collaborator

Why

Follow-up to #4220, which #4223 reverted because the image job failed on PRs from forks (DOCKERHUB_OIDC_CONNECTION_ID must be configured). This PR is rebased on the reverted main, so it re-lands everything #4220 did (parallel jobs, warm Go cache, Build Cloud) and fixes the fork case properly, see "Fork PRs" below. The workflows still duplicated setup boilerplate (Go, Task, Hugo, Hub login) across jobs and workflows, the two image jobs were ~120 near-identical lines, three docs workflows shared identical triggers, every new blocking check required editing the main ruleset, runs on main could be evicted from the concurrency queue (run 34469610126), and the repo sat at the 10 GB cache cap.

Changes

Structure

  • One required check. gate needs every blocking job and passes on success or skip. Adding a blocking job means adding it to gate.needs; the ruleset never changes again. test-windows, cross-compile and image become blocking.
  • One image job. build-image + build-and-push-imageimage; a step decides publish vs cache-only and feeds both build-push steps. publish needs gate + image.
  • Composite actions in .github/actions/, referenced as $/.github/actions/<name> (GitHub's same-repository syntax, resolved from the running commit, no checkout needed): setup-go (Go, Task, caches), setup-hugo, setup-buildx (OIDC login, optional Build Cloud builder). Tool versions are pinned there once.
  • Docs workflows merged. docs-lint, docs-a11y, docs-upstreamdocs.yml, six parallel jobs. models-delta-lint folded into lint.
  • Naming. Lowercase kebab-case everywhere. Jobs: changes, lint, test-linux, test-windows, cross-compile, licenses, image, test-race, gate, publish. Workflows: ci, codeql, docs, docs-deploy, models-update, models-live-check, pr-review, pr-review-trigger, winget.

Build Cloud first, local build as fallback

60% of this repo's PRs come from forks, and bots (Dependabot, the models-update workflow, the PR reviewer) open PRs too. Fork PRs and PRs run with a read-only token receive neither repository variables nor an OIDC token, and the old image condition compared the PR's base repository to this one (always true), so the job ran and failed. image now decides at runtime: if DOCKERHUB_OIDC_CONNECTION_ID and the OIDC token endpoint are present it uses Build Cloud (both platforms, one runner); otherwise it falls back to a plain builder on the runner, linux/amd64 only, nothing pushed, no build-record upload, with a notice in the run. Publishing runs (main, tags) never downgrade: they fail loudly if Build Cloud is unavailable. Same-repo bot PRs (e.g. models-update) keep Build Cloud since they run with the repo's variables and OIDC. CI in a fork of this repo also works, via the local path.

Feedback loop

  • Docs-only PRs skip the Go jobs. changes (dorny/paths-filter) marks a PR docs-only when every changed file is under docs/; Markdown elsewhere is test data and counts as code. Pushes and merge-queue runs always run everything.
  • Cache footprint. Module cache shared and saved once per go.sum change; build cache per job (SHA-suffixed) as before; lint, licenses and CodeQL restore test-linux's build cache without saving their own. Saves happen on main only, via actions/cache's post step, so the per-job save steps are gone. Stale setup-*, buildx-*, buildkit-*, index-* entries purged (150 entries, ~4.3 GB).
  • Never cancel main, tags or merge-queue runs. Their concurrency group includes github.run_id; PR runs still supersede each other.
  • Windows: workspace, Go caches and TEMP excluded from Defender real-time scanning (best effort, continue-on-error). To be measured once main seeds the new cache keys.
  • timeout-minutes on every job (default was 6 h).
  • test-race (task test-race: -race -shuffle=on) on main pushes only, outside gate.

Quality gates and supply chain

  • lint now also runs the parts of task lint CI never ran (project cops, go mod tidy --diff), shellcheck on every script, the models-delta golden test, and zizmor (.github/zizmor.yml keeps the deliberate workflow_run trigger of pr-review). Its findings fixed here: persist-credentials: false on every checkout, Pages permissions scoped to the deploy job.
  • Task and Hugo downloads are checksum-verified; go-licenses pinned to v1.6.0 (what @latest resolved to).
  • Dependabot for GitHub Actions only (workflows + composite actions, weekly, grouped, 7-day cooldown). Go modules stay with the bump-go-dependencies skill.
  • scripts/workflow-lint.sh also checks SHA pins in .github/actions/* and knows the $/ syntax. AGENTS.md gained a GitHub Actions section.
  • actionlint 1.7.12 does not know $/ yet; that single message is ignored via -ignore.

Action versions

All references are authorized by docker/infra-github-allow-list (actions/*, github/*, docker/* wildcarded; third-party actions match exact entries). Bumped to the newest allowed:

action before after
actions/checkout v6.0.3 v7.0.1
actions/setup-go v6.5.0 v7.0.0
actions/deploy-pages v5.0.0 v5.0.1
docker/build-push-action v6.19.2 v7.3.0
docker/setup-buildx-action v3.12.0 v4.3.0
docker/metadata-action v5.10.0 v6.2.0
docker/login-action v3.7.0 v4.6.0
docker/oidc-action v1.1.0 v1.2.0
go-task/setup-task v2.1.0 v2.2.0
peter-evans/create-pull-request v7.0.11 v8.1.1
github/codeql-action v4.37.8 v4.38.0
golangci-lint / Task 2.13.1 / 3.51.1 2.13.2 / 3.53.1
new: dorny/paths-filter v4.0.3, zizmorcore/zizmor-action v0.6.2

Held back: browser-actions/setup-chrome v2.1.2 (v2.2.0 not allow-listed yet), Hugo 0.163.0 (docker/docs still pins it).

Node 20 deprecation: with these bumps every action and every action nested in the composite actions we use declares node24. The only node20 left is actions/download-artifact inside docker/docs's validate-upstream.yml reusable workflow, which is theirs to update.

Repo settings to update, and their impact

1. Before merging: require gate instead of the three old contexts

The "Main rules" ruleset requires build-and-test, license-check, lint, which no longer exist here, so this PR cannot merge until it changes:

gh api -X PUT repos/docker/docker-agent/rulesets/7802083 --input - <<'JSON'
{"name":"Main rules","target":"branch","enforcement":"active",
 "conditions":{"ref_name":{"include":["~DEFAULT_BRANCH"],"exclude":[]}},
 "rules":[{"type":"deletion"},{"type":"non_fast_forward"},{"type":"creation"},{"type":"required_signatures"},
  {"type":"required_status_checks","parameters":{"strict_required_status_checks_policy":false,"do_not_enforce_on_create":false,
   "required_status_checks":[{"context":"gate","integration_id":15368}]}}]}
JSON

Impact: from that moment every other open PR shows gate as "Expected — waiting for status" because their ci.yml does not produce it. Existing PRs must rebase on (or merge) main after this PR lands to become mergeable again. Do it right before merging this PR to keep the window short.

2. After merging: enable the merge queue

Only possible after this lands, because the queue needs the merge_group trigger, which only exists in this PR's ci.yml:

gh api -X PUT repos/docker/docker-agent/rulesets/7802083 --input - <<'JSON'
{"name":"Main rules","target":"branch","enforcement":"active",
 "conditions":{"ref_name":{"include":["~DEFAULT_BRANCH"],"exclude":[]}},
 "rules":[{"type":"deletion"},{"type":"non_fast_forward"},{"type":"creation"},{"type":"required_signatures"},
  {"type":"required_status_checks","parameters":{"strict_required_status_checks_policy":false,"do_not_enforce_on_create":false,
   "required_status_checks":[{"context":"gate","integration_id":15368}]}},
  {"type":"merge_queue","parameters":{"merge_method":"MERGE","max_entries_to_build":5,"min_entries_to_merge":1,"max_entries_to_merge":5,
   "min_entries_to_merge_wait_minutes":5,"grouping_strategy":"ALLGREEN","check_response_timeout_minutes":60}}]}
JSON

Impact: merging becomes "Merge when ready"; the queue tests each PR against the real tip of main before merging, so main is never broken by two green PRs that conflict semantically. PRs that have not rebased since this landed will fail in the queue (their ci.yml has no merge_group trigger, gate never reports, the entry times out after 60 min). Same remedy: rebase. Auto-merge already enabled on the repo keeps working through the queue.

3. Nothing else

Dependabot starts on its own (first run next Monday). The cache purge is already done. The Enterprise ruleset is untouched.

Verified

actionlint (with the $/ ignore), scripts/workflow-lint.sh, shellcheck on every script, zizmor (no findings), go run ./lint ., go mod tidy --diff — all green locally, and the PR run is green on every job including gate (run 34476151145); the ruleset just does not require it yet. test-windows took 15 min on that run: the new cache key prefix means it compiled cold, so the Defender exclusion cannot be judged before main seeds the cache. First-run findings already fixed: go-licenses v2 lives under a /v2 module path; the actionlint action splits flags on whitespace.

The local fallback cannot be exercised from this same-repo PR; the first fork or Dependabot PR after merge is its real test. Not in this PR: making docs a required check (needs the same change-detection pattern), the Windows timing comparison, and the dbc-remediation branch, which rewrites the image jobs too and will conflict.

@aheritier
aheritier requested a review from a team as a code owner September 10, 2026 11:28
@actions-allow-list-sync-readonly

Copy link
Copy Markdown

Allowlist update needed for github/codeql-action/init@b96794f015dfd88f77b49b1c93e0fa7110f94c63. Tracking PR: https://github.com/docker/infra-github-allow-list/pull/261

Please wait for the allowlist PR to merge before merging this PR, otherwise CI will fail because the new SHA isn't allowlisted yet.

@actions-allow-list-sync-readonly

Copy link
Copy Markdown

Allowlist update needed for github/codeql-action/analyze@b96794f015dfd88f77b49b1c93e0fa7110f94c63. Tracking PR: https://github.com/docker/infra-github-allow-list/pull/263

Please wait for the allowlist PR to merge before merging this PR, otherwise CI will fail because the new SHA isn't allowlisted yet.

@actions-allow-list-sync-readonly

Copy link
Copy Markdown

Allowlist update needed for github/codeql-action/autobuild@b96794f015dfd88f77b49b1c93e0fa7110f94c63. Tracking PR: https://github.com/docker/infra-github-allow-list/pull/262

Please wait for the allowlist PR to merge before merging this PR, otherwise CI will fail because the new SHA isn't allowlisted yet.

@aheritier aheritier added area/ci CI/CD workflows and pipeline area/docs Documentation changes kind/chore Maintenance, deps, CI, tooling (maps to chore: commit prefix) labels Sep 10, 2026
@aheritier
aheritier force-pushed the ci-workflow-cleanup branch 2 times, most recently from ae2d7a3 to a9cd2cd Compare September 10, 2026 11:39
@aheritier
aheritier marked this pull request as draft September 10, 2026 11:49
@aheritier
aheritier force-pushed the ci-workflow-cleanup branch 3 times, most recently from 7601fe2 to dc803ea Compare September 10, 2026 12:20
@aheritier
aheritier marked this pull request as ready for review September 10, 2026 13:11
@actions-allow-list-sync-readonly

Copy link
Copy Markdown

Allowlist update for github/codeql-action/init@b96794f015dfd88f77b49b1c93e0fa7110f94c63 was rejected: https://github.com/docker/infra-github-allow-list/pull/261

A maintainer declined to allowlist this action, so CI will keep failing while it is used. Remove the action or switch to an allowed alternative, and contact the security team if you believe this rejection is a mistake.

@actions-allow-list-sync-readonly

Copy link
Copy Markdown

Allowlist update for github/codeql-action/analyze@b96794f015dfd88f77b49b1c93e0fa7110f94c63 was rejected: https://github.com/docker/infra-github-allow-list/pull/263

A maintainer declined to allowlist this action, so CI will keep failing while it is used. Remove the action or switch to an allowed alternative, and contact the security team if you believe this rejection is a mistake.

@actions-allow-list-sync-readonly

Copy link
Copy Markdown

Allowlist update for github/codeql-action/autobuild@b96794f015dfd88f77b49b1c93e0fa7110f94c63 was rejected: https://github.com/docker/infra-github-allow-list/pull/262

A maintainer declined to allowlist this action, so CI will keep failing while it is used. Remove the action or switch to an allowed alternative, and contact the security team if you believe this rejection is a mistake.

dgageot
dgageot previously approved these changes Sep 10, 2026
@aheritier
aheritier marked this pull request as draft September 10, 2026 13:59
@aheritier aheritier added the status/needs-rebase PR has merge conflicts or is out of date with main label Sep 10, 2026
@aheritier

Copy link
Copy Markdown
Collaborator Author

👋 This PR has merge conflicts with the base branch. Please rebase or merge the latest base branch and resolve them. I've moved it to draft and added status/needs-rebase; it'll be picked back up automatically once the conflicts are cleared.

1 similar comment
@aheritier

Copy link
Copy Markdown
Collaborator Author

👋 This PR has merge conflicts with the base branch. Please rebase or merge the latest base branch and resolve them. I've moved it to draft and added status/needs-rebase; it'll be picked back up automatically once the conflicts are cleared.

- `gate` is the single job the main ruleset needs to require: it needs
  every blocking job (now also test-windows, cross-compile and image) and
  passes on success or skip, so adding a check no longer touches the ruleset;
  `publish` needs `gate` instead of listing every job.
- `build-image` and `build-and-push-image` duplicated ~120 lines and differed
  only by exporter/attestations/build args; one `image` job now decides
  publish vs cache-only in a step and feeds both build-push steps.
- OIDC validation, Hub login and builder setup move to `setup-buildx`; Task
  install moves into `setup-go`, which also saves the cache itself on main
  (actions/cache post step) so jobs lose their Save Go caches step.
- `lint` gains the parts of `task lint` CI never ran (project cops, go mod
  tidy) plus shellcheck on every script and the models-delta golden test,
  replacing the models-delta-lint workflow.
- Jobs renamed to short nouns: test, test-windows, cross-compile, licenses,
  image, publish. Actions bumped to the latest allow-listed versions
  (checkout v7.0.1, setup-go v7.0.0, setup-task v2.2.0, docker/* majors on
  Node 24, golangci-lint v2.13.2, Task 3.53.1).
- The run-commit sanity checks that only re-verified actions/checkout are
  dropped; the expected_sha input and OIDC config checks remain.
docs-lint, docs-a11y and docs-upstream shared the same triggers and path
filters; they become parallel jobs of docs.yml (markdownlint, canonical,
links, llms-txt, a11y, upstream). Hugo is installed by a setup-hugo composite
action so the version is pinned once for docs and docs-deploy. Path filters
also cover the composite action and every scripts/docs-*.sh. deploy-pages
bumped to v5.0.1.
Workflow names are lowercase kebab-case (codeql, pr-review, pr-review-trigger,
models-update); the workflow_run reference in pr-review follows the rename.
models-update and models-live-check use the setup-go composite action
restoring the test job's cache instead of their own Go/Task pins.
codeql-action pinned to v4.38.0, create-pull-request to v8.1.1.
The repo sits at the 10 GB cache cap: each main push saved ~1 GB per Go job
because every entry bundled the module cache. setup-go now keeps two caches:
GOMODCACHE keyed on go.sum alone (saved once per dependency change, shared by
every job and OS) and GOCACHE per job with the SHA suffix as before. A `save`
input lets lint, licenses and CodeQL restore the test-linux build cache
without saving a copy of their own.

setup-task verifies the Task archive against the release checksums (per OS)
and setup-hugo does the same for the Hugo tarball. setup-buildx gains a
`builder` input so registry-only jobs skip creating a builder.
…n, zizmor

- `changes` (dorny/paths-filter) marks a PR docs-only when every changed
  file is under docs/; Markdown elsewhere is test data so it still counts as
  code. Go jobs and image need it; lint always runs; gate treats skipped as
  passed. Pushes and merge-queue runs always run everything.
- `merge_group` trigger, with its own concurrency group like main and tags.
- `timeout-minutes` on every job (default was 6 h).
- `test-race` runs `task test-race` (-race -shuffle=on) on main pushes only;
  not part of gate so it never sits on the PR critical path.
- test-windows excludes the workspace, Go caches and TEMP from Defender
  real-time scanning; best effort (continue-on-error).
- zizmor audits workflows and composite actions in lint (min severity low);
  .github/zizmor.yml keeps the deliberate workflow_run trigger of pr-review.
  Its findings fixed here: persist-credentials: false on every checkout.
- Local actions referenced as `$/.github/actions/...` (resolved from the
  running commit, no checkout needed) — also fixes publish, which called a
  ./ action without checking out. actionlint 1.7.12 does not know the syntax
  yet, so that one message is ignored; workflow-lint skips `$/` refs.
- go-licenses pinned to v2.0.1; gate writes a job/result table to the step
  summary; publish no longer creates an unused builder.
…flows

Every job gets timeout-minutes, every checkout persist-credentials: false,
and local actions use the $/ syntax. docs-deploy scopes pages/id-token
permissions to the deploy job. CodeQL's Go analysis restores the test-linux
build cache through setup-go instead of setup-go's shared snapshot.
Weekly grouped updates for workflows and the composite actions under
.github/actions (SHA pins and version comments), with a 7-day cooldown so
releases settle first. Go modules stay with the bump-go-dependencies skill.
…ally

Fork PRs, PRs opened by Dependabot and other bots running with a read-only
token, and CI in forks of this repo receive neither repository variables nor
an OIDC token, so the image job failed on `DOCKERHUB_OIDC_CONNECTION_ID must
be configured` (why #4220 was reverted in #4223). The old condition compared
the PR's *base* repository to this one, which is always true.

The job now decides at runtime: Build Cloud (both platforms, one runner)
whenever the Hub OIDC connection id and the OIDC token endpoint are present,
otherwise a plain docker-container builder on the runner, linux/amd64 only,
nothing pushed, no build-record upload. Publishing runs still fail loudly
when Build Cloud is unavailable rather than downgrading. The Dockerfile is
exercised on every PR instead of being skipped or failing.
@aheritier aheritier removed the status/needs-rebase PR has merge conflicts or is out of date with main label Sep 10, 2026
@aheritier
aheritier marked this pull request as ready for review September 10, 2026 14:38
@aheritier
aheritier merged commit 4c132e1 into main Sep 10, 2026
21 checks passed
@aheritier
aheritier deleted the ci-workflow-cleanup branch September 10, 2026 15:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/ci CI/CD workflows and pipeline area/docs Documentation changes kind/chore Maintenance, deps, CI, tooling (maps to chore: commit prefix)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants