Adds `<version>-alpine`, `<major>-alpine`, and `alpine` tags built from docker/Dockerfile.alpine with the musl release tarball. Alpine users can use the image as a base or copy /opt/pnpm into `node:*-alpine` without fetching the installer from get.pnpm.io, which was returning 403 to bursts of concurrent CI jobs. The download, checksum, and version check move into docker/install-pnpm.sh, shared by both Dockerfiles. The Alpine image also installs libstdc++ and libgcc, which the musl Node.js builds installed by `pnpm runtime` need. The Alpine build runs only for releases that ship musl tarballs for both x64 and arm64, so v11 and v10 releases skip it. Every release used to publish the `latest` and `<major>` tags, so a v11 maintenance release moved `latest` back to v11. The builds now push only the exact version tags. A final step lists the stable releases and, with `docker buildx imagetools create`, points `<major>` at the highest version in that major and `latest` at the highest version overall. The Alpine tags follow the same rule. Deciding after the builds sees every release of a burst, since lines are published seconds apart. A concurrency group was rejected because GitHub cancels a pending run when a third one queues. The selection lives in .github/scripts/docker-image-tags.sh with a test run by CI, and fails on an empty release list. The v12 CI docs now run the Bitbucket Pipelines and GitLab CI examples in `ghcr.io/pnpm/pnpm:12` instead of downloading the installer, and recommend the image for CI that runs jobs in containers. CircleCI keeps the installer because its checkout step needs git and ssh. The CI examples that still use the standalone script fall back to the latest pnpm/get.pnpm.io installer release when get.pnpm.io fails. They run the script only after a download succeeds. Related to pnpm/pnpm#16973.
152 lines
5.8 KiB
YAML
152 lines
5.8 KiB
YAML
name: Docker
|
|
|
|
on:
|
|
release:
|
|
types: [published]
|
|
workflow_dispatch:
|
|
inputs:
|
|
version:
|
|
description: "pnpm version to build (without leading v)"
|
|
required: true
|
|
prerelease:
|
|
description: "Treat as prerelease (skips mutable tags)"
|
|
type: boolean
|
|
default: false
|
|
|
|
jobs:
|
|
build:
|
|
name: Build
|
|
if: github.event_name == 'workflow_dispatch' || startsWith(github.event.release.tag_name, 'v')
|
|
runs-on: blacksmith-8vcpu-ubuntu-2404
|
|
environment: release
|
|
permissions:
|
|
contents: read
|
|
packages: write
|
|
id-token: write
|
|
env:
|
|
IMAGE: ghcr.io/${{ github.repository_owner }}/pnpm
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
with:
|
|
persist-credentials: false
|
|
|
|
- name: Resolve release metadata
|
|
id: meta
|
|
env:
|
|
EVENT_NAME: ${{ github.event_name }}
|
|
RELEASE_TAG: ${{ github.event.release.tag_name }}
|
|
RELEASE_PRERELEASE: ${{ github.event.release.prerelease }}
|
|
INPUT_VERSION: ${{ inputs.version }}
|
|
INPUT_PRERELEASE: ${{ inputs.prerelease }}
|
|
run: |
|
|
set -eu
|
|
if [ "$EVENT_NAME" = "release" ]; then
|
|
version="${RELEASE_TAG#v}"
|
|
prerelease="$RELEASE_PRERELEASE"
|
|
else
|
|
version="${INPUT_VERSION#v}"
|
|
prerelease="$INPUT_PRERELEASE"
|
|
fi
|
|
case "$version" in
|
|
*[!0-9A-Za-z.+-]*) echo "invalid version: $version" >&2; exit 1 ;;
|
|
[0-9]*.[0-9]*.[0-9]*) ;;
|
|
*) echo "invalid version: $version" >&2; exit 1 ;;
|
|
esac
|
|
echo "version=$version" >> "$GITHUB_OUTPUT"
|
|
echo "prerelease=$prerelease" >> "$GITHUB_OUTPUT"
|
|
|
|
- name: Compute pnpm tarball checksums
|
|
id: checksums
|
|
env:
|
|
VERSION: ${{ steps.meta.outputs.version }}
|
|
GH_TOKEN: ${{ github.token }}
|
|
run: |
|
|
set -euo pipefail
|
|
base="https://github.com/pnpm/pnpm/releases/download/v${VERSION}"
|
|
for pair in amd64:x64 arm64:arm64; do
|
|
key="${pair%:*}"
|
|
name="${pair#*:}"
|
|
sha="$(curl -fsSL --retry 3 --retry-delay 2 "${base}/pnpm-linux-${name}.tar.gz" \
|
|
| sha256sum | awk '{print $1}')"
|
|
echo "sha_${key}=${sha}" >> "$GITHUB_OUTPUT"
|
|
done
|
|
# The Alpine image is built only for release lines that ship musl
|
|
# tarballs for both architectures. Older lines lack the arm64 one.
|
|
assets="$(gh release view "v${VERSION}" --repo "$GITHUB_REPOSITORY" --json assets --jq '.assets[].name')"
|
|
alpine=true
|
|
for name in x64 arm64; do
|
|
printf '%s\n' "$assets" | grep -qxF "pnpm-linux-${name}-musl.tar.gz" || alpine=false
|
|
done
|
|
echo "alpine=${alpine}" >> "$GITHUB_OUTPUT"
|
|
if [ "$alpine" = true ]; then
|
|
for pair in amd64:x64 arm64:arm64; do
|
|
key="${pair%:*}"
|
|
name="${pair#*:}"
|
|
sha="$(curl -fsSL --retry 3 --retry-delay 2 "${base}/pnpm-linux-${name}-musl.tar.gz" \
|
|
| sha256sum | awk '{print $1}')"
|
|
echo "sha_musl_${key}=${sha}" >> "$GITHUB_OUTPUT"
|
|
done
|
|
fi
|
|
|
|
- name: Set up QEMU
|
|
uses: docker/setup-qemu-action@99012661954931238ded8c8b007157a8430204e1 # v4.4.0
|
|
|
|
- name: Set up Docker Buildx
|
|
uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069 # v4.4.1
|
|
|
|
- name: Login to GHCR
|
|
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
|
|
with:
|
|
registry: ghcr.io
|
|
username: ${{ github.actor }}
|
|
password: ${{ secrets.GITHUB_TOKEN }}
|
|
|
|
- name: Build and push
|
|
uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7.4.0
|
|
with:
|
|
context: ./docker
|
|
platforms: linux/amd64,linux/arm64
|
|
push: true
|
|
tags: ${{ env.IMAGE }}:${{ steps.meta.outputs.version }}
|
|
build-args: |
|
|
PNPM_VERSION=${{ steps.meta.outputs.version }}
|
|
PNPM_SHA256_AMD64=${{ steps.checksums.outputs.sha_amd64 }}
|
|
PNPM_SHA256_ARM64=${{ steps.checksums.outputs.sha_arm64 }}
|
|
provenance: mode=max
|
|
sbom: true
|
|
|
|
- name: Build and push Alpine
|
|
if: steps.checksums.outputs.alpine == 'true'
|
|
uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7.4.0
|
|
with:
|
|
context: ./docker
|
|
file: ./docker/Dockerfile.alpine
|
|
platforms: linux/amd64,linux/arm64
|
|
push: true
|
|
tags: ${{ env.IMAGE }}:${{ steps.meta.outputs.version }}-alpine
|
|
build-args: |
|
|
PNPM_VERSION=${{ steps.meta.outputs.version }}
|
|
PNPM_SHA256_AMD64=${{ steps.checksums.outputs.sha_musl_amd64 }}
|
|
PNPM_SHA256_ARM64=${{ steps.checksums.outputs.sha_musl_arm64 }}
|
|
provenance: mode=max
|
|
sbom: true
|
|
|
|
# Runs after the builds so that every release published in the same
|
|
# burst is listed by the time this run decides which tags it may move.
|
|
- name: Move mutable tags
|
|
if: steps.meta.outputs.prerelease != 'true'
|
|
env:
|
|
VERSION: ${{ steps.meta.outputs.version }}
|
|
ALPINE: ${{ steps.checksums.outputs.alpine }}
|
|
GH_TOKEN: ${{ github.token }}
|
|
run: |
|
|
set -euo pipefail
|
|
releases="$(gh api --paginate "repos/${GITHUB_REPOSITORY}/releases?per_page=100" \
|
|
--jq '.[] | select(.draft == false and .prerelease == false) | .tag_name')"
|
|
moves="$(.github/scripts/docker-image-tags.sh "$IMAGE" "$VERSION" "$ALPINE" <<< "$releases")"
|
|
while read -r target source; do
|
|
[ -n "$target" ] || continue
|
|
docker buildx imagetools create --tag "$target" "$source"
|
|
done <<< "$moves"
|