Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
117 changes: 100 additions & 17 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,15 @@ permissions:

jobs:
build-and-release:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- runner: ubuntu-latest
goarch: amd64
- runner: ubuntu-24.04-arm
goarch: arm64
runs-on: ${{ matrix.runner }}

steps:
- uses: actions/checkout@v4
Expand All @@ -22,28 +30,100 @@ jobs:
go-version: '1.25.7'
cache: true

- name: Build binary
run: go build -o bin/orchestrator ./go/cmd/orchestrator
- name: Install fpm and rpmbuild
run: |
sudo apt-get update
sudo apt-get install -y ruby ruby-dev build-essential rpm
sudo gem install --no-document fpm

- name: Create tar.gz archive
- name: Build and package
env:
TAG_NAME: ${{ github.ref_name }}
run: tar czf "orchestrator-${TAG_NAME}-linux-amd64.tar.gz" -C bin orchestrator
GOARCH: ${{ matrix.goarch }}
run: |
# strip leading 'v' from tag for package versions
export RELEASE_VERSION="${TAG_NAME#v}"
export GOPATH="$(go env GOPATH)"
./build.sh -a "$GOARCH"

- name: Collect artifacts
run: |
mkdir -p dist
shopt -s nullglob
cp /tmp/orchestrator-release/*.tar.gz dist/ 2>/dev/null || true
cp /tmp/orchestrator-release/*.deb dist/ 2>/dev/null || true
cp /tmp/orchestrator-release/*.rpm dist/ 2>/dev/null || true
ls -la dist/

- name: Upload release assets
uses: softprops/action-gh-release@v2
with:
files: orchestrator-*.tar.gz
files: dist/*
generate_release_notes: true
draft: false
prerelease: ${{ contains(github.ref_name, 'rc') }}

docker:
runs-on: ubuntu-latest
docker-build:
strategy:
fail-fast: false
matrix:
include:
- runner: ubuntu-latest
platform: linux/amd64
arch: amd64
- runner: ubuntu-24.04-arm
platform: linux/arm64
arch: arm64
runs-on: ${{ matrix.runner }}

steps:
- uses: actions/checkout@v4

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Login to GHCR
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Build and push by digest
id: build
uses: docker/build-push-action@v6
with:
context: .
file: docker/Dockerfile
platforms: ${{ matrix.platform }}
outputs: type=image,name=ghcr.io/proxysql/orchestrator,push-by-digest=true,name-canonical=true,push=true

Comment on lines +92 to +100
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🌐 Web query:

Can a GitHub Actions workflow running in a forked repository publish to a hardcoded GHCR image in the upstream owner's namespace (for example ghcr.io/proxysql/orchestrator) using the fork's GITHUB_TOKEN, or must the image name use the fork repository/owner namespace?

💡 Result:

No, a GitHub Actions workflow running in a forked repository cannot publish to a hardcoded GHCR image in the upstream owner's namespace (e.g., ghcr.io/proxysql/orchestrator) using the fork's GITHUB_TOKEN. The GITHUB_TOKEN is scoped to the repository where the workflow runs (the fork) and only grants write access to packages in that repository's namespace (e.g., ghcr.io/forkowner/forkrepo). Publishing to the upstream namespace requires write permission on the upstream package, which the fork's GITHUB_TOKEN lacks. To publish to the upstream image: - Use a Personal Access Token (PAT) with write:packages scope for the upstream owner/org, stored as a secret in the fork. - Grant the fork repository explicit write access to the upstream package via the package settings ("Manage Actions access"). All examples and docs use ${{ github.repository }} or similar for the current repo's namespace, confirming GITHUB_TOKEN does not cross namespaces without explicit package-level grants (which forks typically don't have by default).

Citations:


🏁 Script executed:

# Check if docs/release.md exists and examine line 26 area
fd -t f "release.md" docs/ 2>/dev/null && \
  echo "=== Checking docs/release.md around line 26 ===" && \
  sed -n '20,35p' docs/release.md || echo "docs/release.md not found"

Repository: ProxySQL/orchestrator

Length of output: 782


🏁 Script executed:

# Verify the actual .github/workflows/release.yml file
fd -t f "release.yml" .github/workflows/ 2>/dev/null && \
  echo "=== Checking lines 92-100 ===" && \
  sed -n '92,100p' .github/workflows/release.yml && \
  echo -e "\n=== Checking lines 137-158 ===" && \
  sed -n '137,158p' .github/workflows/release.yml

Repository: ProxySQL/orchestrator

Length of output: 1276


🏁 Script executed:

# Search for all occurrences of the hardcoded namespace
echo "=== All occurrences of 'ghcr.io/proxysql/orchestrator' ===" && \
  rg "ghcr\.io/proxysql/orchestrator" .github/workflows/release.yml -n

Repository: ProxySQL/orchestrator

Length of output: 446


The hardcoded GHCR namespace blocks the documented fork dry-run flow.

The documentation states that RC tags can be used to test the release pipeline end-to-end on a fork, but the hardcoded ghcr.io/proxysql/orchestrator image name at lines 99, 141, 152, and 158 prevents this from working. A fork's GITHUB_TOKEN cannot push to the upstream owner's namespace—it only has write access to its own ghcr.io/${{ github.repository }} namespace. Either derive the image name from the current repository or update the documentation to remove the fork dry-run promise.

Suggested refactor
 jobs:
   build-and-release:
@@
   docker-build:
+    env:
+      IMAGE_NAME: ghcr.io/${{ github.repository }}
     strategy:
@@
     - name: Build and push by digest
       id: build
       uses: docker/build-push-action@v6
       with:
         context: .
         file: docker/Dockerfile
         platforms: ${{ matrix.platform }}
-        outputs: type=image,name=ghcr.io/proxysql/orchestrator,push-by-digest=true,name-canonical=true,push=true
+        outputs: type=image,name=${{ env.IMAGE_NAME }},push-by-digest=true,name-canonical=true,push=true
@@
   docker-merge:
+    env:
+      IMAGE_NAME: ghcr.io/${{ github.repository }}
     needs: docker-build
@@
     - name: Extract metadata
       id: meta
       uses: docker/metadata-action@v5
       with:
-        images: ghcr.io/proxysql/orchestrator
+        images: ${{ env.IMAGE_NAME }}
         tags: |
           type=semver,pattern={{version}}
           type=semver,pattern={{major}}.{{minor}}
           type=raw,value=latest,enable=${{ !contains(github.ref_name, 'rc') }}
@@
     - name: Create manifest list and push
       working-directory: /tmp/digests
       run: |
         docker buildx imagetools create \
           $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
-          $(printf 'ghcr.io/proxysql/orchestrator@sha256:%s ' *)
+          $(printf '${IMAGE_NAME}@sha256:%s ' *)
@@
     - name: Inspect image
       env:
         VERSION: ${{ steps.meta.outputs.version }}
       run: |
-        docker buildx imagetools inspect "ghcr.io/proxysql/orchestrator:${VERSION}"
+        docker buildx imagetools inspect "${IMAGE_NAME}:${VERSION}"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/release.yml around lines 92 - 100, The workflow hardcodes
the image name in the "Build and push by digest" step (outputs: ...
name=ghcr.io/proxysql/orchestrator ...) which prevents forks from pushing;
update the step to derive the image name from the current repo (e.g. use
ghcr.io/${{ github.repository }} or an IMAGE_NAME input/variable) instead of the
hardcoded ghcr.io/proxysql/orchestrator string, and propagate that variable
wherever the hardcoded name is used (the other occurrences called out in the
review) so forks can push to their own GHCR namespace or document that the name
must be overridden.

- name: Export digest
env:
DIGEST: ${{ steps.build.outputs.digest }}
run: |
mkdir -p /tmp/digests
touch "/tmp/digests/${DIGEST#sha256:}"

- name: Upload digest
uses: actions/upload-artifact@v4
with:
name: digests-${{ matrix.arch }}
path: /tmp/digests/*
if-no-files-found: error
retention-days: 1

docker-merge:
needs: docker-build
runs-on: ubuntu-latest
steps:
- name: Download digests
uses: actions/download-artifact@v4
with:
pattern: digests-*
path: /tmp/digests
merge-multiple: true

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

Expand All @@ -64,12 +144,15 @@ jobs:
type=semver,pattern={{major}}.{{minor}}
type=raw,value=latest,enable=${{ !contains(github.ref_name, 'rc') }}

- name: Build and push
uses: docker/build-push-action@v6
with:
push: true
context: .
file: docker/Dockerfile
platforms: linux/amd64
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
- name: Create manifest list and push
working-directory: /tmp/digests
run: |
docker buildx imagetools create \
$(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
$(printf 'ghcr.io/proxysql/orchestrator@sha256:%s ' *)

- name: Inspect image
env:
VERSION: ${{ steps.meta.outputs.version }}
run: |
docker buildx imagetools inspect "ghcr.io/proxysql/orchestrator:${VERSION}"
2 changes: 1 addition & 1 deletion build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ usage() {
echo "-h Show this screen"
echo "-t (linux|darwin) Target OS Default:(linux)"
echo "-i (systemd|sysv) Target init system Default:(systemd)"
echo "-a (amd64|386) Arch Default:(amd64)"
echo "-a (amd64|386|arm64) Arch Default:(amd64)"
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The help message now includes arm64 as a supported architecture. However, the build_binary function (line 125) still hardcodes GOARCH=amd64 for the darwin target. This results in an inconsistency where running ./build.sh -t darwin -a arm64 will produce a tarball labeled as arm64 but containing an amd64 binary. Consider updating build_binary to use the $arch variable for the darwin case as well.

echo "-d debug output"
echo "-b build only, do not generate packages"
echo "-N do not build; use existing ./build/bin/orchestrator binary"
Expand Down
1 change: 1 addition & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
#### Developers
- [Understanding CI](ci.md)
- [Building and testing](build.md)
- [Releasing and publishing packages](release.md)
- [System test environment](ci-env.md)
- [Docker](docker.md)
- [Contributions](contributions.md)
Expand Down
121 changes: 121 additions & 0 deletions docs/release.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
# Releasing and publishing packages

`orchestrator` release artifacts are built and published automatically by the [`Release` workflow](../.github/workflows/release.yml). The workflow fires on any pushed tag matching `v*` and produces:

- `tar.gz`, `.deb`, and `.rpm` packages attached to a GitHub Release
- A multi-arch Docker image pushed to `ghcr.io/proxysql/orchestrator`

Both artifact sets cover `linux/amd64` and `linux/arm64`.

## Triggering a release

```
git tag v4.30.1
git push origin v4.30.1
```

That's the whole trigger. The workflow picks up the tag name as the release version, strips the leading `v`, and uses `4.30.1` for package versions and Docker tags.

### Prerelease (release candidate) tags

Tags that contain `rc` (e.g. `v4.30.1-rc1`) are handled specially:

- The GitHub Release is marked as a prerelease.
- The Docker `latest` tag is **not** updated — only the specific version tags are pushed.

Use this for testing the release pipeline end-to-end on a fork before cutting a real release.

## What runs

The workflow has three jobs:

### 1. `build-and-release` (matrix: amd64, arm64)

Runs on `ubuntu-latest` (amd64) and `ubuntu-24.04-arm` (arm64) — GitHub's native ARM runners, free for public repos. No QEMU, no cross-compilation: each job builds on its own native architecture, so CGO (`go-sqlite3`) works without any special toolchain.
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

There is a discrepancy between the PR description (which mentions using ubuntu-24.04 for each architecture) and this documentation (which mentions ubuntu-latest for amd64). To ensure consistent build environments and GLIBC requirements across architectures, it is recommended to use an explicit version like ubuntu-24.04 for both.

Suggested change
Runs on `ubuntu-latest` (amd64) and `ubuntu-24.04-arm` (arm64) — GitHub's native ARM runners, free for public repos. No QEMU, no cross-compilation: each job builds on its own native architecture, so CGO (`go-sqlite3`) works without any special toolchain.
Runs on ubuntu-24.04 (amd64) and ubuntu-24.04-arm (arm64) — GitHub's native ARM runners, free for public repos. No QEMU, no cross-compilation: each job builds on its own native architecture, so CGO (go-sqlite3) works without any special toolchain.


Each matrix job:

1. Installs Go, `fpm`, and `rpmbuild`.
2. Runs `./build.sh -a <goarch>` with `RELEASE_VERSION` set from the tag.
3. Collects everything `build.sh` writes to `/tmp/orchestrator-release/` and uploads it to the GitHub Release.

Per arch, `build.sh` produces three variants (see `package_linux` in [`build.sh`](../build.sh)):

- `orchestrator` — full package (binary + web resources + sample configs + systemd unit)
- `orchestrator-cli` — binary only
- `orchestrator-client` — the `orchestrator-client` shell script only

Each variant is emitted as `.tar.gz`, `.deb`, and `.rpm`. Package names differ by arch (`_amd64.deb` / `_arm64.deb`, `.x86_64.rpm` / `.aarch64.rpm`), so the two matrix jobs don't collide when uploading to the same Release.
Comment on lines +42 to +48
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Package formats are overstated in this section.

package_linux only emits a .tar.gz for the full orchestrator package. The -cli and -client variants are packaged as .deb/.rpm only, so this text will send readers looking for tarballs that are never produced.

Suggested doc fix
-Each variant is emitted as `.tar.gz`, `.deb`, and `.rpm`. Package names differ by arch (`_amd64.deb` / `_arm64.deb`, `.x86_64.rpm` / `.aarch64.rpm`), so the two matrix jobs don't collide when uploading to the same Release.
+The full `orchestrator` package is emitted as `.tar.gz`, `.deb`, and `.rpm`. The `-cli` and `-client` variants are emitted as `.deb` and `.rpm` only. Package names differ by arch (`_amd64.deb` / `_arm64.deb`, `.x86_64.rpm` / `.aarch64.rpm`), so the two matrix jobs don't collide when uploading to the same Release.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/release.md` around lines 42 - 48, The docs currently claim all three
package variants from package_linux (referencing build.sh and package_linux)
produce .tar.gz, .deb and .rpm; correct this to state that only the full
"orchestrator" variant emits a .tar.gz (in addition to .deb/.rpm if applicable),
while "orchestrator-cli" and "orchestrator-client" are packaged only as
.deb/.rpm; update the paragraph that lists formats for orchestrator,
orchestrator-cli, and orchestrator-client to reflect these exact outputs and
keep the note about arch-specific names/matrix-job upload collisions unchanged.


Both matrix jobs call `softprops/action-gh-release@v2` — the action is idempotent and will attach to the existing Release created by whichever job finishes first.

### 2. `docker-build` (matrix: linux/amd64, linux/arm64)

Each arch builds [`docker/Dockerfile`](../docker/Dockerfile) natively on its own runner and pushes **by digest** to `ghcr.io/proxysql/orchestrator` (no tag yet). The digest is uploaded as a workflow artifact for the merge job to consume.

### 3. `docker-merge`

Downloads both digests, runs `docker/metadata-action` to compute tags from the git tag:

- `type=semver,pattern={{version}}` — e.g. `4.30.1`
- `type=semver,pattern={{major}}.{{minor}}` — e.g. `4.30`
- `type=raw,value=latest` — only when the tag does **not** contain `rc`

Then uses `docker buildx imagetools create` to assemble a multi-arch manifest under all those tags, and inspects the result.

## Verifying a release

### GitHub Release page

After the workflow completes, the Release page should list (for version `X.Y.Z`):

```
orchestrator-X.Y.Z-linux-amd64.tar.gz
orchestrator-X.Y.Z-linux-arm64.tar.gz
orchestrator_X.Y.Z-1_amd64.deb
orchestrator_X.Y.Z-1_arm64.deb
orchestrator-X.Y.Z-1.x86_64.rpm
orchestrator-X.Y.Z-1.aarch64.rpm
```

Plus the `-cli` and `-client` variants in `.deb` and `.rpm` form for each arch.

### Docker manifest

```
docker buildx imagetools inspect ghcr.io/proxysql/orchestrator:X.Y.Z
```

The output should list both `linux/amd64` and `linux/arm64` entries.

Pull and smoke-test each arch:

```
docker run --rm --platform linux/amd64 ghcr.io/proxysql/orchestrator:X.Y.Z orchestrator --version
docker run --rm --platform linux/arm64 ghcr.io/proxysql/orchestrator:X.Y.Z orchestrator --version
```

## Local reproduction

The release workflow does the same thing you can do locally with `build.sh` — see [Building and testing](build.md) and [`build.sh`](../build.sh). To produce ARM64 packages on a non-ARM host (outside of CI), run inside an arm64 container:

```
docker run --rm -it --platform linux/arm64 \
-v $PWD:/src -w /src \
ubuntu:24.04 bash -c '
apt-get update &&
apt-get install -y golang git ruby ruby-dev build-essential rpm &&
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The build.sh script relies on rsync to copy resource artifacts (lines 163-164). The ubuntu:24.04 base image does not include rsync by default, so the local reproduction command provided here will fail. Please add rsync to the apt-get install command.

Suggested change
apt-get install -y golang git ruby ruby-dev build-essential rpm &&
apt-get install -y golang git ruby ruby-dev build-essential rpm rsync &&

gem install --no-document fpm &&
./build.sh -a arm64
'
Comment on lines +100 to +110
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

The local arm64 recipe is missing GOPATH.

build.sh aborts in precheck() when GOPATH is unset, and the workflow already works around that by exporting go env GOPATH first. The container example should do the same or it won't reproduce CI.

Suggested doc fix
     docker run --rm -it --platform linux/arm64 \
       -v $PWD:/src -w /src \
       ubuntu:24.04 bash -c '
         apt-get update &&
         apt-get install -y golang git ruby ruby-dev build-essential rpm &&
         gem install --no-document fpm &&
+        export GOPATH="$(go env GOPATH)" &&
         ./build.sh -a arm64
       '
📝 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
The release workflow does the same thing you can do locally with `build.sh` — see [Building and testing](build.md) and [`build.sh`](../build.sh). To produce ARM64 packages on a non-ARM host (outside of CI), run inside an arm64 container:
```
docker run --rm -it --platform linux/arm64 \
-v $PWD:/src -w /src \
ubuntu:24.04 bash -c '
apt-get update &&
apt-get install -y golang git ruby ruby-dev build-essential rpm &&
gem install --no-document fpm &&
./build.sh -a arm64
'
The release workflow does the same thing you can do locally with `build.sh` — see [Building and testing](build.md) and [`build.sh`](../build.sh). To produce ARM64 packages on a non-ARM host (outside of CI), run inside an arm64 container:
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/release.md` around lines 100 - 110, The Docker example for producing
ARM64 packages is missing GOPATH, causing build.sh to abort in precheck();
update the container command to export GOPATH (e.g. export GOPATH="$(go env
GOPATH)") before invoking ./build.sh -a arm64 so the precheck() sees a valid
GOPATH; ensure the export happens in the same shell session inside the container
(or pass GOPATH via environment in the docker run) so build.sh and its
precheck() succeed.

```

## Permissions and secrets

The workflow relies on `GITHUB_TOKEN` with `contents: write` (for the Release) and `packages: write` (for GHCR) — both declared in [`release.yml`](../.github/workflows/release.yml). No additional secrets are required.

## Troubleshooting

- **Workflow didn't run after tagging.** The tag must start with `v` (see the `on.push.tags` filter). Tags pushed without `git push --tags` or without pushing the specific ref won't trigger it.
- **A matrix job failed mid-way and part of the release is missing.** The workflow uses `fail-fast: false`, so the other arch still completes. Re-running only the failed job from the Actions UI is safe — the GitHub Release and GHCR both accept re-uploads (fpm uses `-f` to overwrite, `action-gh-release` replaces files of the same name).
- **Docker manifest is missing one arch.** If `docker-build` succeeded for only one arch, `docker-merge` will still run but `imagetools create` will produce a single-arch manifest. Re-run the failed `docker-build` job, then re-run `docker-merge`.
Comment on lines +119 to +121
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🌐 Web query:

In GitHub Actions, when a job depends on a matrix job via needs, does the dependent job get skipped if any matrix leg fails and the job has no if: always() condition?

💡 Result:

Yes, in GitHub Actions, when a job depends on a matrix job via needs, the dependent job gets skipped if any matrix leg (individual job instance from the matrix) fails, unless the dependent job has an if: always (or equivalent condition like if: ${{ !failure && !cancelled }}) condition. The official documentation states: "Use jobs..<job_id>.needs to identify any jobs that must complete successfully before this job will run. [...] If a job fails or is skipped, all jobs that need it are skipped unless the jobs use a conditional expression that causes the job to continue." A matrix job is treated as a single logical job composed of multiple legs. If any leg fails (and fail-fast: true default or explicit, or even with fail-fast: false if not all succeed), the matrix job does not complete "successfully." Thus, dependents via needs are skipped without an overriding if condition. This behavior is confirmed across Stack Overflow examples (e.g., test_finish skipped when matrix test has failures) and GitHub issues discussing propagation of failure/skip states through needs dependencies.

Citations:


🏁 Script executed:

# First, find the workflow file(s) referenced in release.md
find . -name "*.md" -path "*/docs/*" | xargs grep -l "docker-merge\|docker-build" | head -5

Repository: ProxySQL/orchestrator

Length of output: 82


🏁 Script executed:

# List GitHub Actions workflow files
git ls-files ".github/workflows/" 2>/dev/null || fd -e ".yml" -e ".yaml" ".github/workflows"

Repository: ProxySQL/orchestrator

Length of output: 213


🏁 Script executed:

# Read the release.yml workflow file to examine docker-merge and docker-build jobs
cat .github/workflows/release.yml

Repository: ProxySQL/orchestrator

Length of output: 4254


Update the Docker manifest recovery note to reflect actual job dependency behavior.

The documentation incorrectly states that docker-merge will run if only one arch succeeds. With docker-merge declared as needs: docker-build and no if: always() condition, a failed docker-build matrix leg causes the entire job to fail, which skips docker-merge. The fail-fast: false setting only allows the other arch to complete; it doesn't prevent the job from failing.

Suggested doc fix
-- **Docker manifest is missing one arch.** If `docker-build` succeeded for only one arch, `docker-merge` will still run but `imagetools create` will produce a single-arch manifest. Re-run the failed `docker-build` job, then re-run `docker-merge`.
+- **Docker manifest is missing one arch.** If either `docker-build` matrix leg fails, `docker-merge` is skipped because it depends on `docker-build`. Re-run the failed `docker-build` job, then re-run `docker-merge`.
📝 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
- **Workflow didn't run after tagging.** The tag must start with `v` (see the `on.push.tags` filter). Tags pushed without `git push --tags` or without pushing the specific ref won't trigger it.
- **A matrix job failed mid-way and part of the release is missing.** The workflow uses `fail-fast: false`, so the other arch still completes. Re-running only the failed job from the Actions UI is safe — the GitHub Release and GHCR both accept re-uploads (fpm uses `-f` to overwrite, `action-gh-release` replaces files of the same name).
- **Docker manifest is missing one arch.** If `docker-build` succeeded for only one arch, `docker-merge` will still run but `imagetools create` will produce a single-arch manifest. Re-run the failed `docker-build` job, then re-run `docker-merge`.
- **Workflow didn't run after tagging.** The tag must start with `v` (see the `on.push.tags` filter). Tags pushed without `git push --tags` or without pushing the specific ref won't trigger it.
- **A matrix job failed mid-way and part of the release is missing.** The workflow uses `fail-fast: false`, so the other arch still completes. Re-running only the failed job from the Actions UI is safe — the GitHub Release and GHCR both accept re-uploads (fpm uses `-f` to overwrite, `action-gh-release` replaces files of the same name).
- **Docker manifest is missing one arch.** If either `docker-build` matrix leg fails, `docker-merge` is skipped because it depends on `docker-build`. Re-run the failed `docker-build` job, then re-run `docker-merge`.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/release.md` around lines 119 - 121, The doc's "Docker manifest is
missing one arch" note is incorrect about job execution: update the text to
state that because the `docker-merge` job has `needs: docker-build` and no `if:
always()`, any failed `docker-build` matrix leg will cause `docker-merge` to be
skipped (even with `fail-fast: false`), so you must re-run the failed
`docker-build` job (or the whole workflow) before `docker-merge` can produce a
multi-arch manifest; mention `imagetools create` will only produce a single-arch
manifest if `docker-merge` runs with only one successful arch and clarify that
re-running the failed `docker-build` leg (not just `docker-merge`) is required
to recover.

1 change: 1 addition & 0 deletions docs/toc.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
#### Developers
- [Understanding CI](ci.md)
- [Building and testing](build.md)
- [Releasing and publishing packages](release.md)
- [System test environment](ci-env.md)
- [Docker](docker.md)
- [Contributions](contributions.md)
Expand Down
Loading