Repository navigation
fix: ghcr.yml - #89
Conversation
📝 WalkthroughWalkthroughThe 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, ChangesGHCR multi-architecture publishing
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
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 inconclusive)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
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
📒 Files selected for processing (1)
.github/workflows/ghcr.yml
| env: | ||
| IMAGE: ghcr.io/${{ github.repository_owner }}/agent |
There was a problem hiding this comment.
🎯 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()}')
PYRepository: 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:
- 1: https://github.com/containers/image/blob/main/docker/reference/normalize.go
- 2: https://quickutil.dev/blog/docker-image-parser-guide/
- 3: https://github.com/distribution/distribution/blob/5e75227fb213162564bab74b146300ffed9f0bbd/docs/content/spec/api.md
- 4: https://github.com/moby/docker-image-spec/blob/main/spec.md
- 5: https://stackoverflow.com/questions/48522615/docker-error-invalid-reference-format-repository-name-must-be-lowercase
🏁 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])
PYRepository: 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]))
PYRepository: 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:
- 1: https://hub.docker.com/r/portabase/agent
- 2: https://hub.docker.com/r/portabase/agent/tags
- 3: https://portabase.io/docs/dashboard/installation/setup
- 4: Portabase/portabase@f4cfd00
- 5: https://github.com/orgs/Portabase/packages/container/package/charts%2Fportabase-agent
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.
| - name: Checkout | ||
| uses: actions/checkout@v4 | ||
| with: | ||
| ref: ${{ inputs.ref }} | ||
| fetch-depth: 0 | ||
| fetch-depth: 1 |
There was a problem hiding this comment.
🔒 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 || trueRepository: 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:
- 1: https://github.com/actions/checkout/blob/v4/action.yml
- 2: https://github.com/actions/checkout/blob/v4.1.1/action.yml
- 3: https://github.com/actions/checkout/blob/v4/README.md
- 4: https://github.com/actions/checkout/?tab=readme-ov-file
🏁 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.")
PYRepository: 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.
| - 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
| 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 }} |
There was a problem hiding this comment.
🎯 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:
- 1: https://github.com/docker/metadata-action?tab=readme-ov-file
- 2: https://github.com/docker/metadata-action/tree/v5.4.0
- 3: Better document
latesttag (flavor.latest=autodefault vstype=raw,value=latest) docker/metadata-action#593 - 4: flavor's suffix not applied to
latestdocker/metadata-action#567
🌐 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:
- 1: How can I get only one image tag in the output? docker/metadata-action#376
- 2: Prevent adding latest tag docker/metadata-action#161
- 3: https://github.com/docker/metadata-action?tab=readme-ov-file
- 4: https://github.com/docker/metadata-action/tree/v5.4.0
- 5: Better document
latesttag (flavor.latest=autodefault vstype=raw,value=latest) docker/metadata-action#593 - 6: https://github.com/marketplace/actions/docker-metadata-action?version=v3.6.0
- 7: https://github.com/docker/metadata-action/blob/master/README.md
- 8: https://cicube.io/workflow-hub/docker-metadata-action/
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.
| - name: Inspect | ||
| run: docker buildx imagetools inspect ${{ env.IMAGE }}:${{ inputs.version }} |
There was a problem hiding this comment.
🎯 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' || trueRepository: 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:
- 1: https://github.com/docker/metadata-action?tab=readme-ov-file
- 2: https://github.com/docker/metadata-action/tree/v5.4.0
- 3: https://github.com/docker/metadata-action/blob/master/README.md
- 4: https://github.com/docker/metadata-action/tree/v5.2.0
- 5: https://github.com/marketplace/actions/docker-metadata-action
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.
| - 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 Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
Summary by CodeRabbit
New Features
latesttag can be generated for supported releases.Bug Fixes