From fbdf5ba19e58e0097f230f6d64fe6302b83eb142 Mon Sep 17 00:00:00 2001 From: UNITRONIX <36471318+UNITRONIX@users.noreply.github.com> Date: Sat, 29 Aug 2026 02:03:25 +0200 Subject: [PATCH] fix(ci): retag GHCR latest on stable release (#387) Stable Release/tag builds were skipped for [version-bump] commits and never moved latest, so Docker installs on :latest stayed on 3.5.4 without the #382 signal fix. Dispatch now publishes latest + VERSION. Refs #387 Thanks: INSOLVE (Honorary); Marco Jakobs (@jacotec); MyNameisStitch (@MyNameisStitch); Redspin (@playerumpknow) --- .github/workflows/docker-publish.yml | 88 ++++++++++++++++------ CHANGELOG.md | 3 + docs/PRE_RELEASE_CHECKLIST.md | 2 +- docs/docker/DOCKER_QUICKSTART.md | 7 +- docs/important/betterdesk-update-flow.md | 2 + docs/important/branching-and-versioning.md | 2 +- 6 files changed, 76 insertions(+), 28 deletions(-) diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index 40e46f98..5afc2e50 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -8,10 +8,14 @@ # # Tags: # - main push: latest, sha- -# - git tag v* / GitHub Release: semver (e.g. 3.0.0-alpha), exact ref (v3.0.0-alpha) -# - workflow_dispatch: optional custom tag via input +# - dev push: dev, sha- +# - git tag v* / GitHub Release (stable): semver, v*, major.minor, latest +# - workflow_dispatch: latest + VERSION when tag empty/latest; else custom tag # -# Triggers: push main (path-filtered), push tag v*, release published, manual +# Skip: branch pushes whose head commit message contains [version-bump]. +# Tag pushes and release/dispatch always build (even when the tagged commit is a bump). +# +# Triggers: push main/dev (path-filtered), push tag v*, release published, manual # ============================================================================= name: Build & Publish Docker Images @@ -20,7 +24,7 @@ on: workflow_dispatch: inputs: tag: - description: 'Image tag (e.g. v3.0.0-alpha or 3.0.0-alpha)' + description: 'Image tag (empty or latest = push latest + VERSION; else custom tag)' required: false default: 'latest' type: string @@ -35,6 +39,7 @@ on: - 'betterdesk-server/**' - 'web-nodejs/**' - '.github/workflows/docker-publish.yml' + - 'VERSION' release: types: [published] @@ -43,15 +48,6 @@ env: REGISTRY: ghcr.io IMAGE_PREFIX: ghcr.io/${{ github.repository_owner }} FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true - # Shared tag rules for docker/metadata-action (keep in sync across build jobs) - DOCKER_METADATA_TAGS: | - type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' && github.event_name == 'push' }} - type=raw,value=dev,enable=${{ github.ref == 'refs/heads/dev' && github.event_name == 'push' }} - type=ref,event=tag - type=semver,pattern={{version}} - type=semver,pattern={{major}}.{{minor}},enable=${{ !contains(github.ref_name, '-') && (github.event_name != 'workflow_dispatch' || !contains(github.event.inputs.tag, '-')) }} - type=raw,value=${{ github.event.inputs.tag }},enable=${{ github.event_name == 'workflow_dispatch' && github.event.inputs.tag != '' && github.event.inputs.tag != 'latest' }} - type=sha,prefix=sha-,enable=${{ github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/dev') }} jobs: read-version: @@ -72,7 +68,13 @@ jobs: build-server: needs: read-version - if: github.event_name != 'push' || !contains(github.event.head_commit.message, '[version-bump]') + # Build on release/dispatch always; on push build tags always; on branch + # push skip [version-bump] commits only. + if: | + github.event_name == 'workflow_dispatch' || + github.event_name == 'release' || + (github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')) || + (github.event_name == 'push' && !contains(github.event.head_commit.message, '[version-bump]')) runs-on: ubuntu-latest permissions: contents: read @@ -97,7 +99,16 @@ jobs: uses: docker/metadata-action@v6 with: images: ${{ env.IMAGE_PREFIX }}/betterdesk-server - tags: ${{ env.DOCKER_METADATA_TAGS }} + tags: | + type=raw,value=latest,enable=${{ (github.ref == 'refs/heads/main' && github.event_name == 'push') || (github.event_name == 'release' && github.event.release.prerelease == false) || (github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') && !contains(github.ref_name, '-')) || (github.event_name == 'workflow_dispatch' && (github.event.inputs.tag == '' || github.event.inputs.tag == 'latest')) }} + type=raw,value=dev,enable=${{ github.ref == 'refs/heads/dev' && github.event_name == 'push' }} + type=ref,event=tag + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}},enable=${{ !contains(github.ref_name, '-') && (github.event_name != 'workflow_dispatch' || !contains(github.event.inputs.tag, '-')) }} + type=raw,value=${{ needs.read-version.outputs.version }},enable=${{ github.event_name == 'workflow_dispatch' && (github.event.inputs.tag == '' || github.event.inputs.tag == 'latest') }} + type=raw,value=v${{ needs.read-version.outputs.version }},enable=${{ github.event_name == 'workflow_dispatch' && (github.event.inputs.tag == '' || github.event.inputs.tag == 'latest') }} + type=raw,value=${{ github.event.inputs.tag }},enable=${{ github.event_name == 'workflow_dispatch' && github.event.inputs.tag != '' && github.event.inputs.tag != 'latest' }} + type=sha,prefix=sha-,enable=${{ github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/dev') }} - name: Resolve image for SBOM/Trivy id: scanref @@ -148,7 +159,11 @@ jobs: build-console: needs: read-version - if: github.event_name != 'push' || !contains(github.event.head_commit.message, '[version-bump]') + if: | + github.event_name == 'workflow_dispatch' || + github.event_name == 'release' || + (github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')) || + (github.event_name == 'push' && !contains(github.event.head_commit.message, '[version-bump]')) runs-on: ubuntu-latest permissions: contents: read @@ -172,7 +187,16 @@ jobs: uses: docker/metadata-action@v6 with: images: ${{ env.IMAGE_PREFIX }}/betterdesk-console - tags: ${{ env.DOCKER_METADATA_TAGS }} + tags: | + type=raw,value=latest,enable=${{ (github.ref == 'refs/heads/main' && github.event_name == 'push') || (github.event_name == 'release' && github.event.release.prerelease == false) || (github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') && !contains(github.ref_name, '-')) || (github.event_name == 'workflow_dispatch' && (github.event.inputs.tag == '' || github.event.inputs.tag == 'latest')) }} + type=raw,value=dev,enable=${{ github.ref == 'refs/heads/dev' && github.event_name == 'push' }} + type=ref,event=tag + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}},enable=${{ !contains(github.ref_name, '-') && (github.event_name != 'workflow_dispatch' || !contains(github.event.inputs.tag, '-')) }} + type=raw,value=${{ needs.read-version.outputs.version }},enable=${{ github.event_name == 'workflow_dispatch' && (github.event.inputs.tag == '' || github.event.inputs.tag == 'latest') }} + type=raw,value=v${{ needs.read-version.outputs.version }},enable=${{ github.event_name == 'workflow_dispatch' && (github.event.inputs.tag == '' || github.event.inputs.tag == 'latest') }} + type=raw,value=${{ github.event.inputs.tag }},enable=${{ github.event_name == 'workflow_dispatch' && github.event.inputs.tag != '' && github.event.inputs.tag != 'latest' }} + type=sha,prefix=sha-,enable=${{ github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/dev') }} - uses: docker/build-push-action@v6 with: @@ -190,7 +214,11 @@ jobs: build-allinone: needs: read-version - if: github.event_name != 'push' || !contains(github.event.head_commit.message, '[version-bump]') + if: | + github.event_name == 'workflow_dispatch' || + github.event_name == 'release' || + (github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')) || + (github.event_name == 'push' && !contains(github.event.head_commit.message, '[version-bump]')) runs-on: ubuntu-latest permissions: contents: read @@ -214,7 +242,16 @@ jobs: uses: docker/metadata-action@v6 with: images: ${{ env.IMAGE_PREFIX }}/betterdesk - tags: ${{ env.DOCKER_METADATA_TAGS }} + tags: | + type=raw,value=latest,enable=${{ (github.ref == 'refs/heads/main' && github.event_name == 'push') || (github.event_name == 'release' && github.event.release.prerelease == false) || (github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') && !contains(github.ref_name, '-')) || (github.event_name == 'workflow_dispatch' && (github.event.inputs.tag == '' || github.event.inputs.tag == 'latest')) }} + type=raw,value=dev,enable=${{ github.ref == 'refs/heads/dev' && github.event_name == 'push' }} + type=ref,event=tag + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}},enable=${{ !contains(github.ref_name, '-') && (github.event_name != 'workflow_dispatch' || !contains(github.event.inputs.tag, '-')) }} + type=raw,value=${{ needs.read-version.outputs.version }},enable=${{ github.event_name == 'workflow_dispatch' && (github.event.inputs.tag == '' || github.event.inputs.tag == 'latest') }} + type=raw,value=v${{ needs.read-version.outputs.version }},enable=${{ github.event_name == 'workflow_dispatch' && (github.event.inputs.tag == '' || github.event.inputs.tag == 'latest') }} + type=raw,value=${{ github.event.inputs.tag }},enable=${{ github.event_name == 'workflow_dispatch' && github.event.inputs.tag != '' && github.event.inputs.tag != 'latest' }} + type=sha,prefix=sha-,enable=${{ github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/dev') }} - uses: docker/build-push-action@v6 with: @@ -234,7 +271,11 @@ jobs: update-description: needs: [build-server, build-console, build-allinone] runs-on: ubuntu-latest - if: github.event_name != 'pull_request' && (github.event_name != 'push' || !contains(github.event.head_commit.message, '[version-bump]')) + if: | + always() && + needs.build-server.result == 'success' && + needs.build-console.result == 'success' && + needs.build-allinone.result == 'success' permissions: contents: read @@ -253,6 +294,7 @@ jobs: server_tags="$(extract_tags "$SERVER_TAGS")" console_tags="$(extract_tags "$CONSOLE_TAGS")" allinone_tags="$(extract_tags "$ALLINONE_TAGS")" + pin_ver="$(tr -d '\n\r' < VERSION 2>/dev/null || echo VERSION)" { echo "## Docker images published" @@ -263,15 +305,15 @@ jobs: echo "| \`ghcr.io/${{ github.repository_owner }}/betterdesk-console\` | ${console_tags:-n/a} |" echo "| \`ghcr.io/${{ github.repository_owner }}/betterdesk\` | ${allinone_tags:-n/a} |" echo "" - echo "### Pin a version (production — official all-in-one)" + echo "### Pin a version (production — recommended)" echo "\`\`\`bash" - echo "export BETTERDESK_IMAGE_TAG=3.0.0" + echo "export BETTERDESK_IMAGE_TAG=${pin_ver}" echo "curl -fsSL https://raw.githubusercontent.com/${{ github.repository }}/main/docker-compose.quick.single.yml -o docker-compose.yml" echo "docker compose pull" echo "docker compose up -d" echo "\`\`\`" echo "" - echo "### Quick start (rolling latest)" + echo "### Rolling stable tip (\`latest\` = last successful stable image publish)" echo "\`\`\`bash" echo "curl -fsSL https://raw.githubusercontent.com/${{ github.repository }}/main/docker-compose.quick.single.yml -o docker-compose.yml" echo "BETTERDESK_IMAGE_TAG=latest docker compose up -d" diff --git a/CHANGELOG.md b/CHANGELOG.md index 73fd4c88..205200cb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ ## [Unreleased] +### Fixed +- **GHCR `:latest` stuck on 3.5.4 while #382 lived only on `:dev` (#387):** `docker-publish.yml` skipped image builds for `[version-bump]` tag pushes, never moved `latest` on Release, and `workflow_dispatch` with default `latest` published no tags. Stable releases now always build, retag `latest` (non-prerelease), and manual dispatch publishes `latest` + current `VERSION`. Prefer pinning compose/`install.sh` semver for production. + ### Changed - _(none yet)_ diff --git a/docs/PRE_RELEASE_CHECKLIST.md b/docs/PRE_RELEASE_CHECKLIST.md index cd6c0269..0ec476f7 100644 --- a/docs/PRE_RELEASE_CHECKLIST.md +++ b/docs/PRE_RELEASE_CHECKLIST.md @@ -143,6 +143,6 @@ Applies only to merges into **`main`** (stable). Version bump, tag, and GitHub R - [ ] **After merge:** verify CI created tag `v` and GitHub Release - [ ] **GHCR image tags:** verify `ghcr.io/unitronix/betterdesk` (official), `betterdesk-server`, and `betterdesk-console` show semver tag (e.g. `3.1.0`) and git ref (`v3.1.0`) after workflow completes - [ ] **Sync dev:** merge `main` → `dev` so development continues from the new minor version -- [ ] **Manual fallback:** Actions → “Build & Publish Docker Images” → Run workflow → tag input `v` if release trigger was skipped +- [ ] **Manual fallback:** Actions → “Build & Publish Docker Images” → Run workflow → leave tag `latest` (publishes `latest` + current `VERSION`) or set tag input `v` if you only need a custom tag. Confirm package tags include both semver and `latest` after a stable release. - [ ] **No secrets in diff:** `git diff --cached` has no API keys / passwords - [ ] **No debug code:** no `console.log` debug statements, no `TODO` in shipped code diff --git a/docs/docker/DOCKER_QUICKSTART.md b/docs/docker/DOCKER_QUICKSTART.md index bcd2edbe..dc2c5a75 100644 --- a/docs/docker/DOCKER_QUICKSTART.md +++ b/docs/docker/DOCKER_QUICKSTART.md @@ -72,11 +72,12 @@ Split layout uses API port **21114**; the official single container uses **21121 ### Pin or change image version ```bash -# Explicit version (recommended for production) +# Explicit release version (recommended for production — matches install.sh / compose default) export BETTERDESK_IMAGE_TAG=3.0.0 docker compose pull && docker compose up -d -# Track rolling latest from main branch builds +# Rolling tip of the last successful *stable* GHCR publish (release / main) +# Not the same as “newest CHANGELOG line” until that release’s images are published export BETTERDESK_IMAGE_TAG=latest docker compose pull && docker compose up -d ``` @@ -196,7 +197,7 @@ docker compose pull && docker compose up -d | Channel | Typical `BETTERDESK_IMAGE_TAG` | |---------|--------------------------------| -| Stable (`main`) | `latest` or a release version (e.g. `3.4.3`) | +| Stable (`main`) | **Pin a release semver** (e.g. `3.5.65` — compose / `install.sh` default). Optional: `latest` = last successful stable image publish on GHCR (may lag a git release until CI publishes). | | Development (`dev`) | `dev` | Then: `docker compose pull && docker compose up -d`. In-app “Install update” stays disabled for official image deployments. diff --git a/docs/important/betterdesk-update-flow.md b/docs/important/betterdesk-update-flow.md index 472c79ca..8fe13c30 100644 --- a/docs/important/betterdesk-update-flow.md +++ b/docs/important/betterdesk-update-flow.md @@ -97,6 +97,8 @@ When the console runs from `ghcr.io/.../betterdesk-console` (see `docker-compose After pulling new images, recreate containers so the console picks up the embedded commit from the new image tag. +**GHCR tags:** prefer pinning `BETTERDESK_IMAGE_TAG` to a release semver (compose / `install.sh` default). `latest` is the rolling tip of the last successful **stable** image publish (Release / `main`); `dev` tracks development branch builds. See [DOCKER_QUICKSTART.md](../docker/DOCKER_QUICKSTART.md) and [#387](https://github.com/UNITRONIX/BetterDesk/issues/387). + ## Update channel (stable / development) Native installs track GitHub **branch HEAD** (not tags) via `UPDATE_GITHUB_BRANCH` in `web-nodejs/.env`: diff --git a/docs/important/branching-and-versioning.md b/docs/important/branching-and-versioning.md index 8366e91e..5ee6a7c8 100644 --- a/docs/important/branching-and-versioning.md +++ b/docs/important/branching-and-versioning.md @@ -115,7 +115,7 @@ UPDATE_GITHUB_BRANCH=dev # development | `version-bump-main.yml` | PR merged to `main` | Minor bump, tag, GitHub Release | | `version-verify.yml` | Push/PR touching version files | Fail if files disagree | | `release-server.yml` | Tag `v*` | Go server binaries | -| `docker-publish.yml` | Tag, release, push to `main`/`dev` | GHCR images | +| `docker-publish.yml` | Tag, release, push to `main`/`dev` | GHCR images: `latest` on stable release/main; `dev` on `dev` pushes; semver on `v*` / Release | ## First stable release note