Skip to content

fix: ghcr.yml - #89

Merged
RambokDev merged 1 commit into
mainfrom
fix/ghcr-ci-cd
Jul 23, 2026
Merged

RambokDev merged 1 commit into
mainfrom
fix/ghcr-ci-cd

Conversation

@RambokDev

@RambokDev RambokDev commented Jul 23, 2026 •

Copy link
Copy Markdown
Contributor

Summary by CodeRabbit

  • New Features

    • Container images are now published with improved multi-architecture support.
    • Version tags now include the exact release version and major/minor version formats.
    • An optional latest tag can be generated for supported releases.
  • Bug Fixes

    • Improved reliability of multi-platform image publishing by assembling and validating the final image manifest after builds complete.

@coderabbitai

coderabbitai Bot commented Jul 23, 2026 •

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The GHCR workflow now builds each target architecture independently, stores pushed image digests as artifacts, and merges them into a tagged multi-architecture manifest. Semantic-version tags include the exact version, major.minor, and optional latest.

Changes

GHCR multi-architecture publishing

Layer / File(s) Summary
Architecture-specific digest builds
.github/workflows/ghcr.yml
The build job derives architecture values, pushes images by digest, exports digest files, and uploads them as temporary artifacts.
Manifest merge and tagging
.github/workflows/ghcr.yml
The merge job downloads digests, creates the multi-architecture manifest with semantic-version tags, and inspects the published manifest.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant BuildJob
  participant GHCR
  participant DigestArtifacts
  participant MergeJob
  BuildJob->>GHCR: Push architecture-specific image by digest
  BuildJob->>DigestArtifacts: Upload image digest
  MergeJob->>DigestArtifacts: Download image digests
  MergeJob->>GHCR: Create and inspect tagged multi-architecture manifest
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Title check ❓ Inconclusive The title only names a file and does not describe the substantive workflow change. Use a concise title that summarizes the main change, such as updating GHCR publishing to a digest-based multi-arch workflow.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/ghcr-ci-cd

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/ghcr.yml:
- Around line 118-122: Add the metadata-action flavor configuration with latest
disabled in the workflow step defining the semver tags, while preserving the
conditional raw latest tag controlled by inputs.add_latest. Ensure
inputs.add_latest: false produces no latest tag.
- Around line 47-51: Update the Checkout step using actions/checkout@v4 to
disable credential persistence by setting persist-credentials to false, while
preserving the existing ref and fetch-depth settings so the GITHUB_TOKEN is not
written into the workspace used by docker/build-push-action.
- Around line 29-30: Update the IMAGE environment value in the workflow to
normalize the repository owner to lowercase before it is consumed by
docker/build-push-action, ensuring the resulting GHCR image reference uses
lowercase path components.
- Around line 131-132: Update the “Inspect” step to reference the
metadata-generated version from steps.meta.outputs.version instead of
inputs.version, so Docker inspects the normalized semver tag produced by the
metadata step.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 5404aacc-482f-4ec7-967c-6d89a8a47fb7

📥 Commits

Reviewing files that changed from the base of the PR and between 4fd50cc and e8bda60.

📒 Files selected for processing (1)
  • .github/workflows/ghcr.yml

Comment on lines +29 to +30
env:
IMAGE: ghcr.io/${{ github.repository_owner }}/agent

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "## workflow excerpt"
if [ -f .github/workflows/ghcr.yml ]; then
  nl -ba .github/workflows/ghcr.yml | sed -n '1,160p'
else
  echo "missing .github/workflows/ghcr.yml"
fi

echo
echo "## action references and env usage"
rg -n "docker/build-push-action|image=|metadata-action|buildx|IMAGE:|github.repository_owner|load|platform" .github/workflows/ghcr.yml || true

echo
echo "## repository owner context from workflow"
python3 - <<'PY'
from pathlib import Path
p=Path('.github/workflows/ghcr.yml')
s=p.read_text() if p.exists() else ''
print('has repo_owner reference:', '${{ github.repository_owner }}' in s)
for i,line in enumerate(s.splitlines(),1):
    if 'github.repository_owner' in line:
        print(f'{i}: {line.strip()}')
PY

Repository: Portabase/agent

Length of output: 213


🌐 Web query:

Docker container registry repository name lowercase paths official documentation

💡 Result:

In the Docker ecosystem, repository names must be lowercase [1][2]. Official technical specifications and implementation references enforce that components within a repository path must consist of lowercase alphanumeric characters, optionally separated by periods, dashes, or underscores [3][4][2]. Key technical details regarding this requirement include: - Strict Enforcement: The Docker runtime and registry tools perform validation checks (such as the regex [a-z0-9]+(?:[._-][a-z0-9]+)* for path components) that explicitly reject uppercase letters [3][1][2]. If you attempt to use uppercase characters in a repository name, you will typically receive an error such as: invalid reference format: repository name must be lowercase [5][2]. - Scope of Constraint: This lowercase requirement applies to the repository path components (the namespace and the repository name itself) [2]. It does not apply to the hostname/domain portion of a reference, nor does it apply to image tags, which are case-sensitive and may contain uppercase letters [4][2]. - Rationale: This constraint is maintained for cross-platform compatibility, particularly to avoid conflicts on file systems that are case-insensitive, such as those often used in Windows or macOS environments [5]. References to these rules can be found in the distribution specification and the source code for Docker reference parsing [3][1][4].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "## workflow excerpt"
if [ -f .github/workflows/ghcr.yml ]; then
  awk '{printf "%6d\t%s\n", NR, $0}' .github/workflows/ghcr.yml | sed -n '1,160p'
else
  echo "missing .github/workflows/ghcr.yml"
fi

echo
echo "## relevant references / calls"
rg -n "docker/build-push-action|image=|metadata-action|buildx|IMAGE:|github\.repository_owner|load|platform" .github/workflows/ghcr.yml || true

echo
echo "## deterministic check of IMAGE expansion for Portabase"
python3 - <<'PY'
from pathlib import Path
p=Path('.github/workflows/ghcr.yml')
s=p.read_text() if p.exists() else ''
owner="Portabase"
image="ghcr.io/${owner}/agent"
print("IMAGE expression value:", "${owner}/agent")
print("expanded image:", image)
print("repository_path_components_lowercase:", image.split("://",1)[1].split("/",1)[0].split("/")[1:2])
PY

Repository: Portabase/agent

Length of output: 6018


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "## repo metadata"
git rev-parse --show-toplevel
git remote -v || true
if [ -f .git/config ]; then
  sed -n '/\[remote/,$p' .git/config
else
  echo "no .git/config"
fi

echo
echo "## workflow variables using owner with sed fallback"
python3 - <<'PY'
from pathlib import Path
p=Path('.github/workflows/ghcr.yml')
text=p.read_text()
for i,line in enumerate(text.splitlines(),1):
    if 'github.repository_owner' in line or 'IMAGE:' in line:
        print(f"{i}: {line}")
PY

echo
echo "## reference lowercase normalization in workflow"
python3 - <<'PY'
value="Portabase/agent"
expected="portabase/agent"
print("owner expansion source value:", value)
print("required registry repository component:", expected)
print("current env value would produce:", f"ghcr.io/{value}/agent")
print("lowercase would produce:", f"ghcr.io/{expected}/agent")
print("mixed-case contains uppercase path segment:", any(part != part.lower() for part in [value]))
PY

Repository: Portabase/agent

Length of output: 1015


🌐 Web query:

ghcr.io Portabase/agent repository exists

💡 Result:

Yes, the ghcr.io/portabase/agent container repository exists. While the primary container image for the Portabase Agent is hosted on Docker Hub as portabase/agent [1][2], the Portabase organization also utilizes the GitHub Container Registry (ghcr.io) for other components, such as its Helm charts (e.g., ghcr.io/portabase/charts/portabase) [3] and has recently added workflows to push images to ghcr.io [4]. Documentation and internal references confirm the project uses the GitHub Container Registry for distributing components [3][5].

Citations:


Normalize (image) to lowercase before Buildx.

IMAGE resolves to ghcr.io/Portabase/agent; repository path components must be lowercase, so the docker/build-push-action image output can be rejected. Use a lowercased value before the Buildx step, e.g. ghcr.io/${{ github.repository_owner }}.image.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/ghcr.yml around lines 29 - 30, Update the IMAGE
environment value in the workflow to normalize the repository owner to lowercase
before it is consumed by docker/build-push-action, ensuring the resulting GHCR
image reference uses lowercase path components.

Comment on lines 47 to +51
- name: Checkout
uses: actions/checkout@v4
with:
ref: ${{ inputs.ref }}
fetch-depth: 0
fetch-depth: 1

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== workflow excerpt =="
sed -n '1,120p' .github/workflows/ghcr.yml 2>/dev/null || true

echo
echo "== workflow files mentioning persist-credentials / actions/checkout =="
rg -n "persist-credentials|actions/checkout@|actions/checkout:" .github/workflows || true

echo
echo "== Dockerfile candidates =="
git ls-files | rg '(^|/)Dockerfile$|(^|/).*Dockerfile.*$' || true

echo
echo "== possible COPY . patterns =="
rg -n "^\s*COPY\s+" .github/workflows || true
rg -n "^\s*Dockerfile|Dockerfile:" .github/workflows || true

echo
echo "== changed files stat =="
git diff --stat || true

Repository: Portabase/agent

Length of output: 4080


🌐 Web query:

actions/checkout v4 persist-credentials default token git credentials

💡 Result:

In actions/checkout v4, the persist-credentials input defaults to true [1][2]. When enabled, this option configures the authentication token (or SSH key) used for the checkout within the local git configuration [3][2]. This default behavior allows subsequent steps in the same job to run authenticated git commands (such as git push or git fetch) without needing to manually re-specify the token or credentials [3][4]. The action manages this by removing the token during post-job cleanup [3]. If you prefer to prevent the credentials from being added to the local git config, you can explicitly opt-out by setting persist-credentials: false in your workflow file [3][4].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== docker/Dockerfile excerpt =="
wc -l docker/Dockerfile
sed -n '1,220p' docker/Dockerfile

echo
echo "== search git directory handling =="
rg -n "^\s*COPY\s+(\.\s*$|\./\s*$|.*\b(\.git|dockerignore)\b)|EXCLUDE|\.gitignore|dockerignore|\.dockerignore" docker/Dockerfile ./.dockerignore .github/workflows/ghcr.yml 2>/dev/null || true

echo
echo "== repository gitignore/dockerignore candidates =="
git ls-files | rg '(^|/)(\.dockerignore|Dockerfile.*)|(^|/)(\..*ignore|.*ignore)$' || true

echo
echo "== current .git existence and action config =="
python3 - <<'PY'
from pathlib import Path
import hashlib, json
for path in [Path('.git'), Path('docker/.dockerignore')]:
    print(f"{path}: exists={path.exists()} type={path.stat().st_mode if path.exists() else None}")
if Path('.git').exists():
    print(".git is present in the repository root, so context: . would include it unless docker context rules ignore it.")
PY

Repository: Portabase/agent

Length of output: 5283


Do not persist the checkout token in the workspace.

actions/checkout@v4 persists secrets.GITHUB_TOKEN by default, and docker/build-push-action@v6 uses context: .. Since COPY . . in docker/Dockerfile builds from the repository root, any .git directory containing local git config includes that token in the build cache/image unless checked out with persist-credentials: false. The GHCR login still has the packages scope it needs.

Proposed fix
       - name: Checkout
         uses: actions/checkout@v4
         with:
           ref: ${{ inputs.ref }}
           fetch-depth: 1
+          persist-credentials: false
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- name: Checkout
uses: actions/checkout@v4
with:
ref: ${{ inputs.ref }}
fetch-depth: 0
fetch-depth: 1
- name: Checkout
uses: actions/checkout@v4
with:
ref: ${{ inputs.ref }}
fetch-depth: 1
persist-credentials: false
🧰 Tools
🪛 zizmor (1.26.1)

[warning] 47-51: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false

(artipacked)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/ghcr.yml around lines 47 - 51, Update the Checkout step
using actions/checkout@v4 to disable credential persistence by setting
persist-credentials to false, while preserving the existing ref and fetch-depth
settings so the GITHUB_TOKEN is not written into the workspace used by
docker/build-push-action.

Source: Linters/SAST tools

Comment on lines 118 to 122
tags: |
type=semver,pattern={{version}},value=${{ inputs.version }}
type=semver,pattern={{major}}.{{minor}},value=${{ inputs.version }}
type=semver,pattern={{major}},value=${{ inputs.version }}
type=raw,value=latest,enable=${{ inputs.add_latest }}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== workflow excerpt =="
fd -a 'ghcr\.ya?ml' .github/workflows || true
file="$(fd 'ghcr\.ya?ml' .github/workflows | head -n1 || true)"
if [ -n "$file" ]; then
  wc -l "$file"
  sed -n '1,180p' "$file" | cat -n
fi

echo "== search docker/metadata action usage/options =="
rg -n "metadata-action|flavor|add_latest|latest|tags:" .github/workflows

echo "== repo files likely related =="
git ls-files .github/workflows | sed -n '1,120p'

Repository: Portabase/agent

Length of output: 7146


🌐 Web query:

docker/metadata-action v5 flavor latest default semver tags auto latest

💡 Result:

In the Docker Metadata Action (v5 and later), the flavor input controls how the latest tag is generated through its latest attribute, which defaults to auto [1][2]. The auto setting means the action implicitly enables a latest tag when using specific tag types that support it, provided they are not superseded by other tag types with higher priority [3][4]. The tag types that implicitly trigger this latest tag behavior include type=ref (event=tag), type=semver, type=pep440, and type=match [1][3]. If you wish to change this behavior, you can explicitly configure the latest attribute within the flavor input: - latest=true: Forces the generation of a latest tag [1]. - latest=false: Disables the generation of an implicit latest tag [3][4]. Additionally, if you are using prefixes or suffixes within the flavor input and want them to apply to the implicitly generated latest tag, you must explicitly enable this by adding,onlatest=true to the prefix or suffix definition [4]. Example configuration: flavor: | latest=auto suffix=-my-suffix,onlatest=true

Citations:


🌐 Web query:

docker/metadata-action v5 latest tag semver patterns flavor latest=false

💡 Result:

In the docker/metadata-action v5, setting flavor: | latest=false prevents the action from automatically generating and adding the latest tag to your output [1][2]. By default, the flavor input is set to latest=auto [3][4]. In auto mode, the action automatically generates a latest tag for specific events and tag types, including type=semver [3][5][6]. Using latest=false explicitly opts out of this behavior [5][2]. For type=semver tags specifically, the action supports patterns to generate versions from Git tags [7][8]. You can define these patterns in the tags input using Handlebars-style expressions [7]: - {{version}}: Shorthand for {{major}}.{{minor}}.{{patch}} [7] - {{major}}: Major version identifier [7] - {{minor}}: Minor version identifier [7] - {{patch}}: Patch version identifier [7] Example usage combining these features: - name: Docker meta id: meta uses: docker/metadata-action@v5 with: images: my-image tags: | type=semver,pattern={{version}} type=semver,pattern={{major}}.{{minor}} flavor: | latest=false This configuration will generate semver-based tags without adding an implicit latest tag [1][2].

Citations:


Disable metadata-action’s implicit latest tag.

docker/metadata-action@v5 defaults to flavor.latest=auto, and type=semver tags emit latest, so inputs.add_latest: false still publishes latest. Add flavor: latest=false and keep the explicit conditional raw tag.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/ghcr.yml around lines 118 - 122, Add the metadata-action
flavor configuration with latest disabled in the workflow step defining the
semver tags, while preserving the conditional raw latest tag controlled by
inputs.add_latest. Ensure inputs.add_latest: false produces no latest tag.

Comment on lines +131 to +132
- name: Inspect
run: docker buildx imagetools inspect ${{ env.IMAGE }}:${{ inputs.version }}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== workflow excerpt =="
sed -n '1,180p' .github/workflows/ghcr.yml 2>/dev/null || true

echo "== docker metadata action usages =="
rg -n "docker/metadata-action|steps\.meta\.outputs|inputs=.*version|version\b" .github/workflows/ghcr.yml .github/workflows 2>/dev/null || true

echo "== changed file context =="
git diff -- .github/workflows/ghcr.yml | sed -n '1,220p' || true

Repository: Portabase/agent

Length of output: 6662


🌐 Web query:

docker/metadata-action v5 semver output {{version}} version output v1.2.3 normalized

💡 Result:

In the docker/metadata-action (v5 and later), the type=semver tag configuration allows you to format version tags using a Handlebars-based pattern [1][2]. When you use the pattern={{version}} expression with a Git tag input of v1.2.3, the action normalizes the output by removing the 'v' prefix, resulting in 1.2.3 [1][3]. The version expression is a shorthand for {{major}}.{{minor}}.{{patch}} [2][4]. The following table summarizes how different patterns affect a v1.2.3 Git tag input [1][3]: | Git tag | Pattern | Output | | --- | --- | --- | | v1.2.3 | {{raw}} | v1.2.3 | | v1.2.3 | {{version}} | 1.2.3 | | v1.2.3 | {{major}}.{{minor}} | 1.2 | | v1.2.3 | v{{major}} | v1 | | v1.2.3 | {{minor}} | 2 | | v1.2.3 | {{patch}} | 3 | This behavior applies when triggered by a push tag event or when a custom value is provided via the value attribute [1][5]. Pre-release versions (e.g., v2.0.8-beta.67) are handled similarly, though they are treated as specific versions and do not always generate shorthand aliases like {{major}}.{{minor}} in the same way as stable releases due to their frequent update nature [2][4].

Citations:


Inspect the metadata-generated version, not raw input.

With type=semver,pattern={{version}},value=${{ inputs.version }}, inputs like v1.2.3 generate the 1.2.3 tag; inspecting ${{ inputs.version }} tries a tag that may not exist. Use steps.meta.outputs.version.

Proposed fix
       - name: Inspect
-        run: docker buildx imagetools inspect ${{ env.IMAGE }}:${{ inputs.version }}
+        env:
+          VERSION: ${{ steps.meta.outputs.version }}
+        run: docker buildx imagetools inspect "${IMAGE}:${VERSION}"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- name: Inspect
run: docker buildx imagetools inspect ${{ env.IMAGE }}:${{ inputs.version }}
- name: Inspect
env:
VERSION: ${{ steps.meta.outputs.version }}
run: docker buildx imagetools inspect "${IMAGE}:${VERSION}"
🧰 Tools
🪛 zizmor (1.26.1)

[warning] 132-132: code injection via template expansion (template-injection): may expand into attacker-controllable code

(template-injection)


[error] 132-132: code injection via template expansion (template-injection): may expand into attacker-controllable code

(template-injection)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/ghcr.yml around lines 131 - 132, Update the “Inspect” step
to reference the metadata-generated version from steps.meta.outputs.version
instead of inputs.version, so Docker inspects the normalized semver tag produced
by the metadata step.

@codecov

codecov Bot commented Jul 23, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@RambokDev
RambokDev merged commit 86a706b into main Jul 23, 2026
3 of 4 checks passed
@RambokDev
RambokDev deleted the fix/ghcr-ci-cd branch July 23, 2026 17:45
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