Skip to content

Conversation

@rezarajan
Copy link
Contributor

@rezarajan rezarajan commented Nov 29, 2025

Summary

This refactor introduces a few key changes to the current CI/CD workflow. Namely, they are focused on addressing the current inefficiencies around double-builds (ci-docker and publish workflows both build, and currently do not share any cache), as well as requiring two runners for simple OCI manifest work. Broadly speaking, this refactor streamlines workflows into two sequential steps: build (ci-docker) and distribute (publish).

Key Changes

  • Split workflows into build (ci-docker) and distribute (publish) steps
  • ci-docker.yml: Build artifacts, caches them, push by digest, export artifacts (digests + ref metadata)
  • publish.yml: Download artifacts from ci-docker, create multi-arch manifests from digests, use type=raw tags to properly set image tags
  • Configuration of all usernames, passwords, and image names via environment variables and secrets, enabling isolated runs across forks without cluttering upstream action runs. Defaults to upstream values.
  • Published images no longer include -amd64 and -arm64v8 suffixes which clutter repositories, but rather publish digests and create a multi-arch manifest which embeds both architectures. Downstream consumers need only concern themselves with the base image tag names, letting the runtime select the appropriate platform.
  • New file: CI_QUICKSTART.md with CI/CD configuration guide

Notes

  • All changes are backward compatible with upstream: no configuration changes required.
  • Most of these changes come from replicating examples from the Docker Docs, which are assumed to be best practice. Regardless, they do make some steps cleaner.
  • Important: GHA caching behaviour should be properly understood as its cache isolation policies may not play well with third-party workflows which trigger builds across different branches. In practice, this is good behaviour as it prevents scenarios like cache pollution and poisoning, but may result in some unexpected scenarios where a developer is expecting a cache hit but fails to pull the cache in the first place. Please see the GitHub Docs for an overview.

Summary by cubic

Streamlined container CI/CD to build once, push by digest, and publish multi-arch manifests. Removes duplicate builds and fixes tag handling (e.g., v1.2.3 -> 1.2.3 and latest for non-prereleases).

  • Refactors

    • Split workflows: ci-docker builds and pushes to GHCR by digest; publish creates manifests and tags.
    • Pass artifacts (digests + ref metadata) from ci-docker to publish to avoid rebuilds.
    • Correct tags via type=raw: branch name, version from vX.Y.Z, and latest only for non-prerelease tags.
    • Drop arch-suffix images; publish a single multi-arch manifest (amd64 + arm64).
    • Use GHA caching per arch for faster builds.
    • Add CI_QUICKSTART.md and env-based config for forks; optional main-branch publish toggle.
  • Migration

    • No breaking changes; upstream configs continue to work.
    • Required: DOCKER_PASSWORD secret and “Read and write” workflow permissions.
    • Optional vars for forks: DOCKER_USERNAME, DOCKER_IMAGE_NAME, GHCR_IMAGE_NAME, ENABLE_UPSTREAM_MAIN_PUBLISH.

Written for commit 0804c78. Summary will update automatically on new commits.

…ation

Modernize workflows by eliminating duplicate builds and fixing tag versioning.
Images are built once in ci-docker.yml and pushed by digest to GHCR. Digest
and ref metadata artifacts enable publish.yml to create multi-arch manifests
and distribute to both registries efficiently.

Key Changes:
- ci-docker.yml: Push by digest, export artifacts (digests + ref metadata)
- publish.yml: Download artifacts, create manifests, use type=raw tags
- Fix workflow_run context issue by passing ref metadata via artifacts
- Tag v1.2.3 now correctly creates 1.2.3 + latest image tags
- Performance: GHA cache used (no rebuild)
- Backward compatible: No configuration changes required

Documentation:
- New file: CI_QUICKSTART.md with configuration guide

Signed-off-by: Reza Rajan <28660160+rezarajan@users.noreply.github.com>
@coderabbitai
Copy link

coderabbitai bot commented Nov 29, 2025

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Tip

📝 Customizable high-level summaries are now available in beta!

You can now customize how CodeRabbit generates the high-level summary in your pull requests — including its content, structure, tone, and formatting.

  • Provide your own instructions using the high_level_summary_instructions setting.
  • Format the summary however you like (bullet lists, tables, multi-section layouts, contributor stats, etc.).
  • Use high_level_summary_in_walkthrough to move the summary from the description to the walkthrough section.

Example instruction:

"Divide the high-level summary into five sections:

  1. 📝 Description — Summarize the main change in 50–60 words, explaining what was done.
  2. 📓 References — List relevant issues, discussions, documentation, or related PRs.
  3. 📦 Dependencies & Requirements — Mention any new/updated dependencies, environment variable changes, or configuration updates.
  4. 📊 Contributor Summary — Include a Markdown table showing contributions:
    | Contributor | Lines Added | Lines Removed | Files Changed |
  5. ✔️ Additional Notes — Add any extra reviewer context.
    Keep each section concise (under 200 words) and use bullet or numbered lists for clarity."

Note: This feature is currently in beta for Pro-tier users, and pricing will be announced later.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@wolf31o2
Copy link
Member

wolf31o2 commented Dec 2, 2025

This is a pretty invasive change set. For one, it publishes build artifacts for all CI builds, which we don't want. CI doesn't push. There's now an implicit dependency between ci-docker and publish which wasn't there before. It strips the revisions which are specific to our releases and the architecture tags, which are useful. This also diverges significantly from our standardized builds, without any prior discussion. I would suggest creating an issue describing the exact problems you're trying to solve, why you think removing existing tags is a good idea, and what you think should be implemented.

@rezarajan
Copy link
Contributor Author

I understand, it is a large change which warrants much needed discussion and alignment on proposed changes. I'll create an issue detailing the existing workflow problems, with reasoning for suggested solutions.

Clarifications

Perhaps a brief discussion of the currently proposed changes will help to iron some things out before creating the issue. Mostly I would like to ensure that my understanding of some of your statements are correct @wolf31o2, and to address them where I think appropriate.

1. Publishing CI Builds

For one, it publishes build artifacts for all CI builds, which we don't want.

These changes do not affect the existing modes of operation of the workflow; in other words CI does not push for all workflows, only ones triggered by a push (to the main branch, and via pattern-matched tags), while workflows triggered by a pull request will only result in a build and cache (isolated from upstream build caches).

As an example, you'll notice that the CI job for this PR only builds the image, and does not push (see here). On the other hand, a push to the fork's main branch does trigger a push to the repository (see here).

This is achieved by using conditionals on the outputs of the docker build-push-action step:

outputs: ${{ github.event_name == 'push' && 'type=image,name-canonical=true,push-by-digest=true,push=true' || 'type=cacheonly' }}

These changes were made in alignment with the Docker Docs for Multi-Platform Builds on GHA.

2. Release and Image Tags

It strips the revisions which are specific to our releases and the architecture tags, which are useful.

While the proposed version does indeed strip architecture tags (but retains the multi-platform manifest), it does not behave any different than the current implementation for releases, except that it also pushes pre-release images. If I have misinterpreted your statement, I would appreciate it if you may clarify what you mean - this may help in detailing the issue. Please see my overview below:

Current Implementation

If I understand correctly, the current implementation foregoes registry pushes which include -pre- in the ref tag

if: startsWith(github.ref, 'refs/tags/') && ! contains(github.ref, '-pre-')

, but does include them as a GH pre-release

prerelease: ${{ (startsWith(github.ref, 'refs/tags/') && contains(github.ref, '-pre-')) && true || false }},

This means any tag associated with the main branch, and which matches the trigger pattern v.*.*, and which does contain -pre- should have an associated release, but not a container image. Otherwise, all other tags which satisfy the trigger conditions should have both an associated release and container image.

New Implementation

Similarly, this is also done in the proposed implementation, with added patterns typically associated with pre-releases:

# Check if prerelease based on tag name
if [[ "$REF_NAME" =~ -pre-|-rc|-alpha|-beta|-testci ]]; then
echo "is_prerelease=true" >> $GITHUB_OUTPUT
else
echo "is_prerelease=false" >> $GITHUB_OUTPUT
fi

The metadata step also ensures the same image naming convention (excluding the 'v' in the tag), and omits associating 'latest' with pre-releases.

tags: |
# Branch name for branch pushes (e.g., main)
type=raw,value=${{ steps.ref.outputs.ref_name }},enable=${{ steps.ref.outputs.ref_type == 'branch' }}
# Version tag for tag pushes (e.g., v1.2.3 -> 1.2.3)
type=raw,value=${{ steps.ref.outputs.version }},enable=${{ steps.ref.outputs.ref_type == 'tag' }}
# :latest for non-prerelease tags only
type=raw,value=latest,enable=${{ steps.ref.outputs.is_latest == 'true' }}

The GitHub release also properly sets the pre-release field:

prerelease: ${{ steps.ref.outputs.is_prerelease == 'true' }},

Again, if I have misunderstood anything, please let me know.


I'll also just note here, for reference, that these changes follow the prior cache changes discussed in #304

@wolf31o2
Copy link
Member

wolf31o2 commented Dec 3, 2025

I don't understand what benefit there is in upheaving all of this, especially with the loss of the tagging we currently use. We currently use the architecture-specific tags. We also do not use "-pre" versioning, but we do use our own release revision tags, since we do not control the upstream versioning. I want to preserve these. This code changes the entire system without understanding why much of it is done. Break these changes up into smaller pieces, not giant refactors which can potentially break desired behaviors. While there's room for improvements, CI that needs its own README is definitely overkill.

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.

2 participants