Skip to content

feat(github-actions): publish coder-workspace images to GHCR on release - #878

Open
ppat wants to merge 3 commits into
mainfrom
feat/ghcr-publish
Open

feat(github-actions): publish coder-workspace images to GHCR on release#878
ppat wants to merge 3 commits into
mainfrom
feat/ghcr-publish

Conversation

@ppat

@ppat ppat commented Aug 20, 2026

Copy link
Copy Markdown
Owner

Summary

Adopts ppat/github-workflows build-docker-image.yaml v6.0.0 for the publish-image job in
.github/workflows/release.yaml, and enables publishing the released coder-workspace image to
GHCR (ghcr.io/ppat/coder-workspace) alongside the existing private-registry push.

v6.0.0 is not merged yet. This PR pins to the head SHA of the still-open
ppat/github-workflows#615 (a6829b4b819f7c39adabf277c7d87a1e663c186d). Do not merge this PR
until that PR merges and a v6.0.0 tag exists
— at that point the pin comment should be updated
to reference the tag SHA instead of the PR-branch SHA (same content, since the PR branch doesn't
move once merged, but the comment currently says so explicitly as a merge blocker).

v6 breaking changes and how this PR adapts to them

  1. source_git_ref renamed to git_ref. Updated the one call site.

  2. image_id output removed. Verified via git grep -n image_id — this repo never referenced
    it, so no follow-up needed.

  3. New optional ghcr_repository input. No secrets required; GHCR authenticates with the
    ambient GITHUB_TOKEN. Wired conditionally:

    ghcr_repository: ${{ needs.create-release.outputs.released_version != 'v0.0.0' && 'ppat/coder-workspace' || '' }}

    publish-image runs unconditionally — on real releases and on PR/dry-run/workflow_dispatch
    test builds — but GHCR should only receive real releases. release-semantic.yaml (pinned here
    at v5.0.1, unchanged) emits the literal sentinel v0.0.0 for released_version on every
    non-publishing path (PR event, or workflow_dispatch with test_publish: true) and a real
    vX.Y.Z only when it actually cuts a release. This mirrors the idiom publish-template already
    uses one job over (released_version != 'v0.0.0'), so the two publish paths (GHCR image,
    Coder template) agree on what counts as "a real release."

    Traced both branches:

    • PR build: dry_run: true → release-semantic's dry-run path → released_version == 'v0.0.0'
      ghcr_repository evaluates to ''PUBLISH_GHCR is false → no GHCR push.
    • Real release (workflow_dispatch, test_publish: false): dry_run: false → an actual
      semantic-release run → released_version is a real vX.Y.Zghcr_repository evaluates
      to 'ppat/coder-workspace' → image published to GHCR.
    • workflow_dispatch with test_publish: true also resolves to the dry-run path above, so it
      does not publish either — correct, it's still a test run.
  4. packages: write is now required unconditionally by the called workflow's build-image
    job
    , even for callers that never touch GHCR. Quoting the workflow's own comment (and the
    failure mode it documents):

    a job's permissions: block accepts no expressions, so it cannot be made conditional...
    a caller that does not itself grant packages: write gets a hard failure at workflow LOAD:
    zero jobs run, no annotations are produced, only a top-level error of the form:

    The nested job 'build-image' is requesting 'packages: write', but is only allowed 'packages: none'.

    release.yaml has no workflow-level permissions: block anywhere (all jobs run at the repo
    default, read), so this PR adds a job-level block on publish-image only:

    permissions:
      contents: read
      packages: write

    create-release and publish-template are untouched — create-release authenticates via a
    GitHub App token (HOMELAB_BOT_APP_* secrets passed into release-semantic.yaml), not
    GITHUB_TOKEN, so it needs no contents: write, and neither job touches GHCR.

What this PR does NOT touch

Two ppat/github-workflows pins exist in this file. Only the build-docker-image.yaml one (used
by publish-image) is bumped. The release-semantic.yaml pin (used by create-release) is left
at v5.0.1 — unrelated to this change.

Test coverage — what's actually exercised by this PR's own CI

This PR's own pull_request trigger only exercises the non-publishing path:
create-release runs with dry_run: true, so released_version == 'v0.0.0' and
ghcr_repository resolves to ''publish-image still runs (private-registry push, as
today), but the new GHCR branch of the conditional is not exercised by this PR's checks. The
real-release path (ghcr_repository resolving non-empty and an image actually landing in GHCR)
can only be exercised by an actual workflow_dispatch release run after merge. What this PR's CI
does prove: the workflow still loads and runs with the new packages: write grant in place (no
startup_failure), and the renamed git_ref input is accepted.

Local checks

  • pre-commit run --files .github/workflows/release.yaml — all hooks pass (yamllint, whitespace,
    etc.)
  • commitlint: could not run locally in this sandbox — commitlint's cached pre-commit node env
    fails to resolve @commitlint/ensure's subpath export under this environment's Node 26 (an
    environment issue reproducible even against a message copied verbatim from CLAUDE.md's own
    example, not something introduced by this change). Header is 71 characters, well under the
    120-char limit, type/scope are both in the allowed list. Relying on this repo's CI
    lint-commit-messages job as the authoritative check.

ppat added 2 commits August 20, 2026 08:08
Adopts ppat/github-workflows build-docker-image.yaml v6.0.0 (currently
unmerged at ppat/github-workflows#615 -- the pin below targets that PR's
head SHA and must be repointed to the v6.0.0 tag once it merges).

- source_git_ref -> git_ref (input renamed in v6, breaking)
- image_id output removed in v6; unused here, no change needed
- new ghcr_repository input wired to publish only on real releases,
  gated the same way publish-template already gates its own coder
  template push (released_version != 'v0.0.0'), so PR/dry-run/
  workflow_dispatch test builds never publish to GHCR
- publish-image now declares job-level `packages: write` -- required
  unconditionally by the called workflow even though this repo also
  publishes to a private registry, because a job's `permissions:`
  block cannot be made conditional; omitting it fails the whole
  workflow at load time, not just the GHCR step
…s fixed head

The prior pin predated a fix for a malformed-repository input that left the
publish gate on while warning it was disabled, producing a broken image
reference and a hard failure. Repoint to the current head so this build
validates against the fixed behaviour. The release-semantic.yaml pin is
untouched.
@ppat

ppat commented Aug 20, 2026

Copy link
Copy Markdown
Owner Author

Repointed the build-docker-image.yaml pin (only) from a6829b4 to the current head of ppat/github-workflows#615 (59629d5); the release-semantic.yaml pin at v5.0.1 is untouched. The old SHA predated a fix for a malformed-repository input that warned "publish is disabled" while leaving the publish gate on, producing a broken image reference and a hard failure — this build was validating that broken behaviour. Force-refreshing CI to build against the fix.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant