diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 274c214..c6bdc07 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -36,23 +36,56 @@ jobs: # TODO: To be implemented # test-job: # uses: ./.github/workflows/ci.yml - build-job: - # TODO: To be implemented - # needs: test-job + setup: runs-on: ubuntu-latest + outputs: + matrix: ${{ steps.set-matrix.outputs.matrix }} steps: - - name: Checkout code - uses: actions/checkout@v5 + - uses: actions/checkout@v5 + - uses: oven-sh/setup-bun@v1 - - uses: oven-sh/setup-bun@635640504f6d7197d3bb29876a652f671028dc97 - - run: bun install + - name: Download versions.json + run: gh release download versions -p versions.json || echo "{}" > versions.json + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - name: Check for new releases of nodejs and bun - if: ${{ inputs.nodejs-version == '' && inputs.bun-versions == '' && inputs.distros == '' }} + - name: Cleanup unsupported versions run: | - set -e - echo "NODE_VERSIONS_TO_BUILD=$(bun run check-bun-node.ts --node ${{ env.NODE_MAJOR_VERSIONS_TO_CHECK }})" >> $GITHUB_ENV - echo "BUN_VERSIONS_TO_BUILD=$(bun run check-bun-node.ts --bun ${{ env.BUN_TAGS_TO_CHECK }})" >> $GITHUB_ENV + bun run check-bun-node.ts --cleanup --generate + if [[ `git status --porcelain` ]]; then + git config --local user.email "github-actions[bot]@users.noreply.github.com" + git config --local user.name "github-actions[bot]" + git add . + git commit -m "chore: cleanup unsupported versions" + git push + fi + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Generate Matrix + id: set-matrix + run: | + MATRIX=$(bun run check-bun-node.ts --matrix) + echo "matrix=$MATRIX" >> $GITHUB_OUTPUT + env: + BUN_TAGS_TO_CHECK: ${{ env.BUN_TAGS_TO_CHECK }} + DISTROS: ${{ env.DISTROS }} + NODE_MAJOR_VERSIONS_TO_CHECK: ${{ env.NODE_MAJOR_VERSIONS_TO_CHECK }} + + build: + needs: setup + runs-on: ubuntu-latest + if: ${{ fromJson(needs.setup.outputs.matrix).include[0] }} + strategy: + fail-fast: false + matrix: ${{ fromJson(needs.setup.outputs.matrix) }} + steps: + - uses: actions/checkout@v5 + + - name: Download versions.json + run: gh release download versions -p versions.json || echo "{}" > versions.json + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Setup Docker Buildx uses: docker/setup-buildx-action@v3 @@ -69,29 +102,64 @@ jobs: timeout_minutes: 120 max_attempts: 3 retry_on: error - command: ./build_updated.sh + command: ./build_single.sh --bun "${{ matrix.bun_version }}" --node "${{ matrix.node_version }}" --distro "${{ matrix.distro }}" env: REGISTRY: ${{ env.REGISTRY }} PLATFORMS: ${{ env.PLATFORMS }} - NODE_VERSIONS_TO_BUILD: ${{ env.NODE_VERSIONS_TO_BUILD || inputs.nodejs-version }} - BUN_VERSIONS_TO_BUILD: ${{ env.BUN_VERSIONS_TO_BUILD || inputs.bun-versions }} - DISTROS: ${{ env.DISTROS || inputs.distros }} - - name: Commit changes - run: ./commit_changes.sh + - name: Upload success artifact + uses: actions/upload-artifact@v4 + with: + name: build-success-${{ matrix.bun_version }}-${{ matrix.node_version }}-${{ matrix.distro }} + path: build_success.json + + update-release: + needs: build + runs-on: ubuntu-latest + if: always() && needs.build.result == 'success' + steps: + - uses: actions/checkout@v5 + - uses: oven-sh/setup-bun@v1 + + - name: Download versions.json + run: gh release download versions -p versions.json || echo "{}" > versions.json env: - BUN_VERSIONS_TO_BUILD: ${{ env.BUN_VERSIONS_TO_BUILD }} - NODE_VERSIONS_TO_BUILD: ${{ env.NODE_VERSIONS_TO_BUILD }} - DISTROS: ${{ env.DISTROS }} - - name: Pull changes - run: git pull -r - - name: Push changes - uses: ad-m/github-push-action@77c5b412c50b723d2a4fbc6d71fb5723bcd439aa + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Download all artifacts + uses: actions/download-artifact@v4 with: - github_token: ${{ secrets.GITHUB_TOKEN }} + pattern: build-success-* + merge-multiple: true + path: updates + + - name: Update versions.json + run: | + # Merge all JSON fragments into versions.json + # We iterate over all files in updates/ and merge them using jq + # Note: This is a simple merge. If multiple builds update the same key, last one wins (which is fine as they should be identical for the same version). + + for file in updates/*.json; do + if [ -f "$file" ]; then + # Merge nodejs versions + # We need to be careful not to overwrite the whole object if we only have partial updates. + # jq's * operator merges objects recursively. + jq -s '.[0] * .[1]' versions.json "$file" > versions.json.tmp && mv versions.json.tmp versions.json + fi + done + + cat versions.json + + - name: Upload versions.json + run: | + gh release create versions versions.json --title "Versions State" --notes "Stores the current versions.json state" || \ + gh release upload versions versions.json --clobber + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + rerun-failed-jobs: runs-on: ubuntu-latest - needs: [build-job] + needs: [build] if: failure() steps: - name: Rerun failed jobs in the current workflow diff --git a/build_single.sh b/build_single.sh new file mode 100755 index 0000000..3776f5b --- /dev/null +++ b/build_single.sh @@ -0,0 +1,144 @@ +#!/bin/bash + +# Exit on error +set -e + +# Logging function +log() { + echo "[$(date +'%Y-%m-%dT%H:%M:%S%z')] $@" +} + +# Retry function +retry() { + local retries=${RETRIES:-3} + local count=0 + until "$@"; do + exit_code=$? + count=$((count + 1)) + if [ $count -lt $retries ]; then + log "Retrying ($count/$retries)..." + sleep 5 + else + log "Failed after $count attempts." + return $exit_code + fi + done + return 0 +} + +# Parse arguments +while [[ "$#" -gt 0 ]]; do + case $1 in + --bun) BUN_VERSION="$2"; shift ;; + --node) NODE_VERSION="$2"; shift ;; + --distro) DISTRO="$2"; shift ;; + *) echo "Unknown parameter passed: $1"; exit 1 ;; + esac + shift +done + +if [ -z "$BUN_VERSION" ] || [ -z "$NODE_VERSION" ] || [ -z "$DISTRO" ]; then + echo "Usage: $0 --bun --node --distro " + exit 1 +fi + +log "Building image for Bun version $BUN_VERSION, Node version $NODE_VERSION, Distro $DISTRO" + +# Read versions.json for codename lookup (optional, but good for tagging) +# If versions.json is not present, we might miss some tags, but the matrix generation should have ensured we have what we need. +# Actually, we need versions.json to know the codename (e.g. "Iron") and to check if it's "latest". +# We can expect versions.json to be present in the working directory (downloaded by workflow). + +if [ -f "versions.json" ]; then + json_data=$(cat versions.json) +else + json_data="{}" +fi + +REGISTRY=${REGISTRY:-imbios} +PLATFORMS=${PLATFORMS:-linux/amd64,linux/arm64} + +generate_tags() { + local bun_version=$1 + local node_version=$2 + local distro=$3 + + local node_major=${node_version%%.*} + local node_minor=${node_version%.*} + local bun_major=${bun_version%%.*} + local bun_minor=${bun_version%.*} + + local is_canary=false + if [[ $bun_version == *"-canary"* ]]; then + is_canary=true + bun_version="canary" + fi + + echo "$REGISTRY/bun-node:${bun_version}-${node_version}-${distro}" + + if [ $is_canary == false ]; then + echo "$REGISTRY/bun-node:${bun_minor}-${node_version}-${distro}" + echo "$REGISTRY/bun-node:${bun_major}-${node_version}-${distro}" + echo "$REGISTRY/bun-node:${bun_version}-${node_minor}-${distro}" + echo "$REGISTRY/bun-node:${bun_version}-${node_major}-${distro}" + elif [[ $bun_version == "canary" ]]; then + echo "$REGISTRY/bun-node:canary-${node_minor}-${distro}" + echo "$REGISTRY/bun-node:canary-${node_major}-${distro}" + fi + + local codename=$(echo "${json_data}" | jq -r ".nodejs.\"${node_major}\".name") + if [ "$codename" != "null" ]; then + echo "$REGISTRY/bun-node:${bun_version}-${codename}-${distro}" + if [[ $is_canary == false ]]; then + echo "$REGISTRY/bun-node:latest-${codename}-${distro}" + fi + fi + + if [[ $is_canary == false ]]; then + echo "$REGISTRY/bun-node:latest-${node_version}-${distro}" + echo "$REGISTRY/bun-node:latest-${node_major}-${distro}" + fi + + # Latest tag logic + # We rely on the caller (workflow) or check versions.json to see if this is the latest Node version. + local latest_node_major=$(echo "${json_data}" | jq -r '.nodejs | keys | map(tonumber) | max') + + if [[ $is_canary == false && "$node_major" == "$latest_node_major" && $distro == "debian" ]]; then + echo "$REGISTRY/bun-node:latest" + fi + + if [[ $is_canary == false ]]; then + echo "$REGISTRY/bun-node:${node_major}-${distro}" + fi +} + +tag_distro=$DISTRO +if [ "$DISTRO" == "debian-slim" ]; then + tag_distro="slim" +fi + +tags=($(generate_tags "$BUN_VERSION" "$NODE_VERSION" "$tag_distro")) +image_name="$REGISTRY/bun-node:${BUN_VERSION}-${NODE_VERSION}-${tag_distro}" + +node_major=${NODE_VERSION%%.*} + +for tag in "${tags[@]}"; do + log "Tagging $image_name as $tag" + retry docker buildx build --sbom=true --provenance=true --platform "$PLATFORMS" -t "$image_name" -t "$tag" "./src/base/${node_major}/${DISTRO}" --push --provenance=mode=max + + if [ "$DISTRO" == "alpine" ]; then + log "Building and Tagging Alpine image with Git" + retry docker buildx build --sbom=true --provenance=true --platform "$PLATFORMS" -t "$image_name-git" -t "$tag-git" "./src/git/${node_major}/${DISTRO}" --push --provenance=mode=max + fi +done + +# Output success JSON fragment for aggregation +# We need to update versions.json with the new versions. +# We output a JSON file that the workflow can pick up. +bun_tag="latest" +if [[ $BUN_VERSION == *"-canary"* ]]; then + bun_tag="canary" +fi + +# Create a partial JSON update +echo "{\"nodejs\": {\"$node_major\": {\"version\": \"v$NODE_VERSION\"}}, \"bun\": {\"$bun_tag\": \"v$BUN_VERSION\"}}" > build_success.json diff --git a/build_updated.sh b/build_updated.sh deleted file mode 100755 index 942b1d3..0000000 --- a/build_updated.sh +++ /dev/null @@ -1,162 +0,0 @@ -#!/bin/bash - -# Exit on error -set -e - -# Logging function -log() { - echo "[$(date +'%Y-%m-%dT%H:%M:%S%z')] $@" -} - -# Function to validate version format -validate_version() { - local version=$1 - local regex="^[0-9]+\.[0-9]+\.[0-9]+(-canary\.[0-9]{8}\.[0-9]+)?$" - if [[ ! $version =~ $regex ]]; then - echo "Invalid version format: $version" - exit 1 - fi -} - -# Retry function -retry() { - local retries=${RETRIES:-3} - local count=0 - until "$@"; do - exit_code=$? - count=$((count + 1)) - if [ $count -lt $retries ]; then - log "Retrying ($count/$retries)..." - sleep 5 - else - log "Failed after $count attempts." - return $exit_code - fi - done - return 0 -} - -# Convert comma-separated strings to arrays -IFS=',' read -ra NODE_VERSIONS <<<"$NODE_VERSIONS_TO_BUILD" -IFS=',' read -ra BUN_VERSIONS <<<"$BUN_VERSIONS_TO_BUILD" -IFS=',' read -ra DISTROS <<<"$DISTROS" - -# If NODE_VERSIONS_TO_BUILD is empty, but BUN_VERSIONS_TO_BUILD is not, -# build all versions from versions.json -if [ -z "$NODE_VERSIONS_TO_BUILD" ]; then - IFS=',' read -ra NODE_MAJOR_VERSIONS <<<"$NODE_MAJOR_VERSIONS_TO_CHECK" - NODE_VERSIONS=() - for node_major_version in "${NODE_MAJOR_VERSIONS[@]}"; do - node_version=$(cat versions.json | jq -r ".nodejs.\"${node_major_version}\".version") - if [ "$node_version" != "null" ]; then - NODE_VERSIONS+=("${node_version//v/}") - fi - done -fi - -log "Building Node versions: ${NODE_VERSIONS[*]}" - -# If BUN_VERSIONS_TO_BUILD is empty, but NODE_VERSIONS_TO_BUILD is not, -# build all versions from versions.json -if [ -z "$BUN_VERSIONS_TO_BUILD" ]; then - BUN_VERSIONS=() - for bun_version in $(cat versions.json | jq -r '.bun | keys[]'); do - BUN_VERSIONS+=("${bun_version//v/}") - done -fi - -log "Building Bun versions: ${BUN_VERSIONS[*]}" - -# Validate versions -for version in "${NODE_VERSIONS[@]}"; do - validate_version "$version" -done -for version in "${BUN_VERSIONS[@]}"; do - validate_version "$version" -done - -# Read the JSON file -json_data=$(cat versions.json) - -# Function to generate tags -generate_tags() { - local bun_version=$1 - local node_version=$2 - local distro=$3 - - local node_major=${node_version%%.*} - local node_minor=${node_version%.*} - local bun_major=${bun_version%%.*} - local bun_minor=${bun_version%.*} - - local is_canary=false - if [[ $bun_version == *"-canary"* ]]; then - is_canary=true - bun_version="canary" - fi - - echo "$REGISTRY/bun-node:${bun_version}-${node_version}-${distro}" - - if [ $is_canary == false ]; then - echo "$REGISTRY/bun-node:${bun_minor}-${node_version}-${distro}" - echo "$REGISTRY/bun-node:${bun_major}-${node_version}-${distro}" - echo "$REGISTRY/bun-node:${bun_version}-${node_minor}-${distro}" - echo "$REGISTRY/bun-node:${bun_version}-${node_major}-${distro}" - elif [[ $bun_version == "canary" ]]; then - echo "$REGISTRY/bun-node:canary-${node_minor}-${distro}" - echo "$REGISTRY/bun-node:canary-${node_major}-${distro}" - fi - - local codename=$(echo "${json_data}" | jq -r ".nodejs.\"${node_major}\".name") - echo "$REGISTRY/bun-node:${bun_version}-${codename}-${distro}" - - if [[ $is_canary == false ]]; then - echo "$REGISTRY/bun-node:latest-${node_version}-${distro}" - echo "$REGISTRY/bun-node:latest-${node_major}-${distro}" - echo "$REGISTRY/bun-node:latest-${codename}-${distro}" - fi - - if [[ $is_canary == false && "$node_major" == "20" && $distro == "debian" ]]; then - echo "$REGISTRY/bun-node:latest" - fi - - if [[ $is_canary == false ]]; then - echo "$REGISTRY/bun-node:${node_major}-${distro}" - fi -} - -# Build, tag, and push loop -for bun_version in "${BUN_VERSIONS[@]}"; do - for node_version in "${NODE_VERSIONS[@]}"; do - for distro in "${DISTROS[@]}"; do - tag_distro=$distro - if [ "$distro" == "debian-slim" ]; then - tag_distro="slim" - fi - - tags=($(generate_tags "$bun_version" "$node_version" "$tag_distro")) - - node_major=${node_version%%.*} - log "Building image for Bun version $bun_version, Node version $node_version, Distro $distro" - image_name="$REGISTRY/bun-node:${bun_version}-${node_version}-${tag_distro}" - - for tag in "${tags[@]}"; do - log "Tagging $image_name as $tag" - retry docker buildx build --sbom=true --provenance=true --platform "$PLATFORMS" -t "$image_name" -t "$tag" "./src/base/${node_major}/${distro}" --push --provenance=mode=max - - if [ "$distro" == "alpine" ]; then - log "Building and Tagging Alpine image with Git" - retry docker buildx build --sbom=true --provenance=true --platform "$PLATFORMS" -t "$image_name-git" -t "$tag-git" "./src/git/${node_major}/${distro}" --push --provenance=mode=max - fi - done - - log "Updating versions.json file" - bun_tag="latest" - if [[ $bun_version == *"-canary"* ]]; then - bun_tag="canary" - fi - json_data=$(echo "${json_data}" | jq ".nodejs.\"${node_major}\".version = \"v${node_version}\"" | jq ".bun.\"${bun_tag}\" = \"v${bun_version}\"") - echo "${json_data}" >versions.json - done - done -done diff --git a/check-bun-node.ts b/check-bun-node.ts index 2c87ff6..ec0f703 100644 --- a/check-bun-node.ts +++ b/check-bun-node.ts @@ -8,9 +8,356 @@ // @ts-expect-error - no types import nodevu from "@nodevu/core"; import { $ } from "bun"; +import { mkdir, readFile, rm, writeFile } from "node:fs/promises"; +import { join } from "node:path"; const nodevuData = await nodevu({ fetch }); +const DEBIAN_TEMPLATE = (major: number) => `FROM debian:bookworm-slim AS build + +# https://github.com/oven-sh/bun/releases +ARG BUN_VERSION=latest + +# Node.js includes python3 for node-gyp, see https://github.com/oven-sh/bun/issues/9807 +# Though, not on slim and alpine images. +RUN apt-get update -qq \\ + && apt-get install -qq --no-install-recommends \\ + ca-certificates \\ + curl \\ + dirmngr \\ + gpg \\ + gpg-agent \\ + unzip \\ + python3 \\ + && apt-get clean \\ + && rm -rf /var/lib/apt/lists/* \\ + && arch="$(dpkg --print-architecture)" \\ + && case "\${arch##*-}" in \\ + amd64) build="x64-baseline";; \\ + arm64) build="aarch64";; \\ + *) echo "error: unsupported architecture: $arch"; exit 1 ;; \\ + esac \\ + && version="$BUN_VERSION" \\ + && case "$version" in \\ + latest | canary | bun-v*) tag="$version"; ;; \\ + v*) tag="bun-$version"; ;; \\ + *) tag="bun-v$version"; ;; \\ + esac \\ + && case "$tag" in \\ + latest) release="latest/download"; ;; \\ + *) release="download/$tag"; ;; \\ + esac \\ + && curl "https://github.com/oven-sh/bun/releases/$release/bun-linux-$build.zip" \\ + -fsSLO \\ + --compressed \\ + --retry 5 \\ + || (echo "error: failed to download: $tag" && exit 1) \\ + && for key in \\ + "F3DCC08A8572C0749B3E18888EAB4D40A7B22B59" \\ + ; do \\ + gpg --batch --keyserver hkps://keys.openpgp.org --recv-keys "$key" \\ + || gpg --batch --keyserver keyserver.ubuntu.com --recv-keys "$key" ; \\ + done \\ + && curl "https://github.com/oven-sh/bun/releases/$release/SHASUMS256.txt.asc" \\ + -fsSLO \\ + --compressed \\ + --retry 5 \\ + && gpg --batch --decrypt --output SHASUMS256.txt SHASUMS256.txt.asc \\ + || (echo "error: failed to verify: $tag" && exit 1) \\ + && grep " bun-linux-$build.zip\\$" SHASUMS256.txt | sha256sum -c - \\ + || (echo "error: failed to verify: $tag" && exit 1) \\ + && unzip "bun-linux-$build.zip" \\ + && mv "bun-linux-$build/bun" /usr/local/bin/bun \\ + && rm -f "bun-linux-$build.zip" SHASUMS256.txt.asc SHASUMS256.txt \\ + && chmod +x /usr/local/bin/bun + +FROM node:${major}-bookworm + +COPY docker-entrypoint.sh /usr/local/bin +COPY --from=build /usr/local/bin/bun /usr/local/bin/bun +RUN mkdir -p /usr/local/bun-node-fallback-bin && ln -s /usr/local/bin/bun /usr/local/bun-node-fallback-bin/node +ENV PATH "\${PATH}:/usr/local/bun-node-fallback-bin" + +# Disable the runtime transpiler cache by default inside Docker containers. +# On ephemeral containers, the cache is not useful +ARG BUN_RUNTIME_TRANSPILER_CACHE_PATH=0 +ENV BUN_RUNTIME_TRANSPILER_CACHE_PATH=\${BUN_RUNTIME_TRANSPILER_CACHE_PATH} + +# Ensure \`bun install -g\` works +ARG BUN_INSTALL_BIN=/usr/local/bin +ENV BUN_INSTALL_BIN=\${BUN_INSTALL_BIN} + +RUN groupadd bun \\ + --gid 1001 \\ + && useradd bun \\ + --uid 1001 \\ + --gid bun \\ + --shell /bin/sh \\ + --create-home \\ + && ln -s /usr/local/bin/bun /usr/local/bin/bunx \\ + && which bun \\ + && which bunx \\ + && bun --version + +WORKDIR /home/bun/app +ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"] +CMD ["/usr/local/bin/bun"] +`; + +const SLIM_TEMPLATE = (major: number) => `FROM debian:bookworm-slim AS build + +# https://github.com/oven-sh/bun/releases +ARG BUN_VERSION=latest + +RUN apt-get update -qq \\ + && apt-get install -qq --no-install-recommends \\ + ca-certificates \\ + curl \\ + dirmngr \\ + gpg \\ + gpg-agent \\ + unzip \\ + && apt-get clean \\ + && rm -rf /var/lib/apt/lists/* \\ + && arch="$(dpkg --print-architecture)" \\ + && case "\${arch##*-}" in \\ + amd64) build="x64-baseline";; \\ + arm64) build="aarch64";; \\ + *) echo "error: unsupported architecture: $arch"; exit 1 ;; \\ + esac \\ + && version="$BUN_VERSION" \\ + && case "$version" in \\ + latest | canary | bun-v*) tag="$version"; ;; \\ + v*) tag="bun-$version"; ;; \\ + *) tag="bun-v$version"; ;; \\ + esac \\ + && case "$tag" in \\ + latest) release="latest/download"; ;; \\ + *) release="download/$tag"; ;; \\ + esac \\ + && curl "https://github.com/oven-sh/bun/releases/$release/bun-linux-$build.zip" \\ + -fsSLO \\ + --compressed \\ + --retry 5 \\ + || (echo "error: failed to download: $tag" && exit 1) \\ + && for key in \\ + "F3DCC08A8572C0749B3E18888EAB4D40A7B22B59" \\ + ; do \\ + gpg --batch --keyserver hkps://keys.openpgp.org --recv-keys "$key" \\ + || gpg --batch --keyserver keyserver.ubuntu.com --recv-keys "$key" ; \\ + done \\ + && curl "https://github.com/oven-sh/bun/releases/$release/SHASUMS256.txt.asc" \\ + -fsSLO \\ + --compressed \\ + --retry 5 \\ + && gpg --batch --decrypt --output SHASUMS256.txt SHASUMS256.txt.asc \\ + || (echo "error: failed to verify: $tag" && exit 1) \\ + && grep " bun-linux-$build.zip\\$" SHASUMS256.txt | sha256sum -c - \\ + || (echo "error: failed to verify: $tag" && exit 1) \\ + && unzip "bun-linux-$build.zip" \\ + && mv "bun-linux-$build/bun" /usr/local/bin/bun \\ + && rm -f "bun-linux-$build.zip" SHASUMS256.txt.asc SHASUMS256.txt \\ + && chmod +x /usr/local/bin/bun \\ + && which bun \\ + && bun --version + +FROM node:${major}-bookworm-slim + +# Disable the runtime transpiler cache by default inside Docker containers. +# On ephemeral containers, the cache is not useful +ARG BUN_RUNTIME_TRANSPILER_CACHE_PATH=0 +ENV BUN_RUNTIME_TRANSPILER_CACHE_PATH=\${BUN_RUNTIME_TRANSPILER_CACHE_PATH} + +# Ensure \`bun install -g\` works +ARG BUN_INSTALL_BIN=/usr/local/bin +ENV BUN_INSTALL_BIN=\${BUN_INSTALL_BIN} + +COPY docker-entrypoint.sh /usr/local/bin +COPY --from=build /usr/local/bin/bun /usr/local/bin/bun +RUN mkdir -p /usr/local/bun-node-fallback-bin && ln -s /usr/local/bin/bun /usr/local/bun-node-fallback-bin/node +ENV PATH "\${PATH}:/usr/local/bun-node-fallback-bin" + +RUN groupadd bun \\ + --gid 1001 \\ + && useradd bun \\ + --uid 1001 \\ + --gid bun \\ + --shell /bin/sh \\ + --create-home \\ + && ln -s /usr/local/bin/bun /usr/local/bin/bunx \\ + && which bun \\ + && which bunx \\ + && bun --version + +WORKDIR /home/bun/app +ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"] +CMD ["/usr/local/bin/bun"] +`; + +const ALPINE_TEMPLATE = (major: number, alpineVer: string) => `FROM alpine:${alpineVer} AS build + +# https://github.com/oven-sh/bun/releases +ARG BUN_VERSION=latest + +RUN apk --no-cache add ca-certificates curl dirmngr gpg gpg-agent unzip \\ + && arch="$(apk --print-arch)" \\ + && case "\${arch##*-}" in \\ + x86_64) build="x64-musl-baseline";; \\ + aarch64) build="aarch64-musl";; \\ + *) echo "error: unsupported architecture: $arch"; exit 1 ;; \\ + esac \\ + && version="$BUN_VERSION" \\ + && case "$version" in \\ + latest | canary | bun-v*) tag="$version"; ;; \\ + v*) tag="bun-$version"; ;; \\ + *) tag="bun-v$version"; ;; \\ + esac \\ + && case "$tag" in \\ + latest) release="latest/download"; ;; \\ + *) release="download/$tag"; ;; \\ + esac \\ + && curl "https://github.com/oven-sh/bun/releases/$release/bun-linux-$build.zip" \\ + -fsSLO \\ + --compressed \\ + --retry 5 \\ + || (echo "error: failed to download: $tag" && exit 1) \\ + && for key in \\ + "F3DCC08A8572C0749B3E18888EAB4D40A7B22B59" \\ + ; do \\ + gpg --batch --keyserver hkps://keys.openpgp.org --recv-keys "$key" \\ + || gpg --batch --keyserver keyserver.ubuntu.com --recv-keys "$key" ; \\ + done \\ + && curl "https://github.com/oven-sh/bun/releases/$release/SHASUMS256.txt.asc" \\ + -fsSLO \\ + --compressed \\ + --retry 5 \\ + && gpg --batch --decrypt --output SHASUMS256.txt SHASUMS256.txt.asc \\ + || (echo "error: failed to verify: $tag" && exit 1) \\ + && grep " bun-linux-$build.zip\\$" SHASUMS256.txt | sha256sum -c - \\ + || (echo "error: failed to verify: $tag" && exit 1) \\ + && unzip "bun-linux-$build.zip" \\ + && mv "bun-linux-$build/bun" /usr/local/bin/bun \\ + && rm -f "bun-linux-$build.zip" SHASUMS256.txt.asc SHASUMS256.txt \\ + && chmod +x /usr/local/bin/bun + +FROM node:${major}-alpine${alpineVer} + +# Disable the runtime transpiler cache by default inside Docker containers. +# On ephemeral containers, the cache is not useful +ARG BUN_RUNTIME_TRANSPILER_CACHE_PATH=0 +ENV BUN_RUNTIME_TRANSPILER_CACHE_PATH=\${BUN_RUNTIME_TRANSPILER_CACHE_PATH} + +# Ensure \`bun install -g\` works +ARG BUN_INSTALL_BIN=/usr/local/bin +ENV BUN_INSTALL_BIN=\${BUN_INSTALL_BIN} + +COPY --from=build /usr/local/bin/bun /usr/local/bin/ +COPY docker-entrypoint.sh /usr/local/bin/ +RUN mkdir -p /usr/local/bun-node-fallback-bin && ln -s /usr/local/bin/bun /usr/local/bun-node-fallback-bin/node +ENV PATH "\${PATH}:/usr/local/bun-node-fallback-bin" + +# Temporarily use the \`build\`-stage /tmp folder to access the glibc APKs: +RUN --mount=type=bind,from=build,source=/tmp,target=/tmp \\ + addgroup -g 1001 bun \\ + && adduser -u 1001 -G bun -s /bin/sh -D bun \\ + && ln -s /usr/local/bin/bun /usr/local/bin/bunx \\ + && apk add libgcc libstdc++ \\ + && which bun \\ + && which bunx \\ + && bun --version + +WORKDIR /home/bun/app +ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"] +CMD ["/usr/local/bin/bun"] +`; + +const GIT_ALPINE_TEMPLATE = (major: number, alpineVer: string) => `FROM alpine:${alpineVer} AS build + +# https://github.com/oven-sh/bun/releases +ARG BUN_VERSION=latest + +RUN apk --no-cache add ca-certificates curl dirmngr gpg gpg-agent unzip \\ + && arch="$(apk --print-arch)" \\ + && case "\${arch##*-}" in \\ + x86_64) build="x64-musl-baseline";; \\ + aarch64) build="aarch64-musl";; \\ + *) echo "error: unsupported architecture: $arch"; exit 1 ;; \\ + esac \\ + && version="$BUN_VERSION" \\ + && case "$version" in \\ + latest | canary | bun-v*) tag="$version"; ;; \\ + v*) tag="bun-$version"; ;; \\ + *) tag="bun-v$version"; ;; \\ + esac \\ + && case "$tag" in \\ + latest) release="latest/download"; ;; \\ + *) release="download/$tag"; ;; \\ + esac \\ + && curl "https://github.com/oven-sh/bun/releases/$release/bun-linux-$build.zip" \\ + -fsSLO \\ + --compressed \\ + --retry 5 \\ + || (echo "error: failed to download: $tag" && exit 1) \\ + && for key in \\ + "F3DCC08A8572C0749B3E18888EAB4D40A7B22B59" \\ + ; do \\ + gpg --batch --keyserver hkps://keys.openpgp.org --recv-keys "$key" \\ + || gpg --batch --keyserver keyserver.ubuntu.com --recv-keys "$key" ; \\ + done \\ + && curl "https://github.com/oven-sh/bun/releases/$release/SHASUMS256.txt.asc" \\ + -fsSLO \\ + --compressed \\ + --retry 5 \\ + && gpg --batch --decrypt --output SHASUMS256.txt SHASUMS256.txt.asc \\ + || (echo "error: failed to verify: $tag" && exit 1) \\ + && grep " bun-linux-$build.zip\\$" SHASUMS256.txt | sha256sum -c - \\ + || (echo "error: failed to verify: $tag" && exit 1) \\ + && unzip "bun-linux-$build.zip" \\ + && mv "bun-linux-$build/bun" /usr/local/bin/bun \\ + && rm -f "bun-linux-$build.zip" SHASUMS256.txt.asc SHASUMS256.txt \\ + && chmod +x /usr/local/bin/bun + +FROM node:${major}-alpine${alpineVer} + +# Disable the runtime transpiler cache by default inside Docker containers. +# On ephemeral containers, the cache is not useful +ARG BUN_RUNTIME_TRANSPILER_CACHE_PATH=0 +ENV BUN_RUNTIME_TRANSPILER_CACHE_PATH=\${BUN_RUNTIME_TRANSPILER_CACHE_PATH} + +# Ensure \`bun install -g\` works +ARG BUN_INSTALL_BIN=/usr/local/bin +ENV BUN_INSTALL_BIN=\${BUN_INSTALL_BIN} + +COPY --from=build /usr/local/bin/bun /usr/local/bin/ +COPY docker-entrypoint.sh /usr/local/bin/ +RUN mkdir -p /usr/local/bun-node-fallback-bin && ln -s /usr/local/bin/bun /usr/local/bun-node-fallback-bin/node +ENV PATH "\${PATH}:/usr/local/bun-node-fallback-bin" + +# Temporarily use the \`build\`-stage /tmp folder to access the glibc APKs: +RUN --mount=type=bind,from=build,source=/tmp,target=/tmp \\ + addgroup -g 1001 bun \\ + && adduser -u 1001 -G bun -s /bin/sh -D bun \\ + && ln -s /usr/local/bin/bun /usr/local/bin/bunx \\ + && apk add libgcc libstdc++ \\ + && which bun \\ + && which bunx \\ + && bun --version + +# Add git +RUN apk fix && \\ + apk --no-cache --update add git git-lfs gpg less openssh patch && \\ + git lfs install + +WORKDIR /home/bun/app +ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"] +CMD ["/usr/local/bin/bun"] +`; + + + + + // ... existing logic ... + /** * Filters Node.js release data to return only major releases with documented support. */ @@ -191,24 +538,269 @@ async function getVersions( /** * This will detect, wether --bun or --node is requested */ + +async function getAlpineVersion(major: number) { + try { + const response = await fetch( + `https://hub.docker.com/v2/repositories/library/node/tags/?page_size=100&name=${major}-alpine` + ); + if (!response.ok) throw new Error("Failed to fetch tags"); + const data = await response.json() as any; + // Find tags that match exactly ${major}-alpine3.x + const tags = data.results + .map((r: any) => r.name) + .filter((name: string) => new RegExp(`^${major}-alpine3\\.\\d+$`).test(name)); + + // Sort to find latest (e.g. alpine3.20 > alpine3.19) + tags.sort((a: string, b: string) => { + const verA = parseFloat(a.split("alpine")[1] || "0"); + const verB = parseFloat(b.split("alpine")[1] || "0"); + return verB - verA; + }); + + if (tags.length > 0 && tags[0]) { + // Extract 3.20 from 20-alpine3.20 + return tags[0].split("alpine")[1] || "3.20"; + } + return "3.20"; // Fallback + } catch (e) { + console.error("Error fetching Alpine version:", e); + return "3.20"; // Fallback + } +} + +async function generateDockerfiles() { + const releases = await generateReleaseData(); + const supportedMajors = releases + .filter((r) => r && ["Current", "Active LTS", "Maintenance LTS"].includes(r.status)) + .map((r) => r?.major); + + // Find a source for docker-entrypoint.sh + let entrypointSource = ""; + const glob = new Bun.Glob("**/docker-entrypoint.sh"); + for await (const file of glob.scan("src")) { + entrypointSource = file; + break; + } + + if (!entrypointSource) { + console.error("Could not find docker-entrypoint.sh to copy!"); + return; + } + const entrypointContent = await readFile(join("src", entrypointSource)); + + for (const major of supportedMajors) { + if (!major) continue; + + const alpineVer = await getAlpineVersion(major); + console.log(`Generating for Node ${major} (Alpine ${alpineVer})`); + + // src/base + const baseDir = join("src/base", major.toString()); + + // Debian + const debianDir = join(baseDir, "debian"); + await mkdir(debianDir, { recursive: true }); + await writeFile(join(debianDir, "dockerfile"), DEBIAN_TEMPLATE(major)); + await writeFile(join(debianDir, "docker-entrypoint.sh"), entrypointContent); + await $`chmod +x ${join(debianDir, "docker-entrypoint.sh")}`; + + // Slim + const slimDir = join(baseDir, "debian-slim"); + await mkdir(slimDir, { recursive: true }); + await writeFile(join(slimDir, "dockerfile"), SLIM_TEMPLATE(major)); + await writeFile(join(slimDir, "docker-entrypoint.sh"), entrypointContent); + await $`chmod +x ${join(slimDir, "docker-entrypoint.sh")}`; + + // Alpine + const alpineDir = join(baseDir, "alpine"); + await mkdir(alpineDir, { recursive: true }); + await writeFile(join(alpineDir, "dockerfile"), ALPINE_TEMPLATE(major, alpineVer)); + await writeFile(join(alpineDir, "docker-entrypoint.sh"), entrypointContent); + await $`chmod +x ${join(alpineDir, "docker-entrypoint.sh")}`; + + // src/git + const gitDir = join("src/git", major.toString(), "alpine"); + await mkdir(gitDir, { recursive: true }); + await writeFile(join(gitDir, "dockerfile"), GIT_ALPINE_TEMPLATE(major, alpineVer)); + await writeFile(join(gitDir, "docker-entrypoint.sh"), entrypointContent); + await $`chmod +x ${join(gitDir, "docker-entrypoint.sh")}`; + } +} + +/** + * Clean up unsupported Node.js version folders in src/base and src/git + */ +async function cleanup() { + const releases = await generateReleaseData(); + const supportedMajors = releases + .filter((r) => r && ["Current", "Active LTS", "Maintenance LTS"].includes(r.status)) + .map((r) => r?.major.toString()); + + const dirsToClean = ["src/base", "src/git"]; + + for (const dir of dirsToClean) { + const absoluteDir = join(process.cwd(), dir); + const glob = new Bun.Glob("*"); + for await (const folder of glob.scan({ cwd: absoluteDir, absolute: false, onlyFiles: false })) { + if (!supportedMajors.includes(folder)) { + const folderPath = join(absoluteDir, folder); + try { + const stats = await (await import("node:fs/promises")).stat(folderPath); + if (stats.isDirectory()) { + await rm(folderPath, { recursive: true, force: true }); + } + } catch (e) { + // Ignore errors (e.g., file does not exist) + } + } + } + } +} + +/** + * Generate Matrix for GitHub Actions + */ +async function generateMatrix() { + const releases = (await generateReleaseData()).filter(Boolean); + let versionsJson: any = {}; + try { + versionsJson = await Bun.file("versions.json").json(); + } catch { + // If file doesn't exist (first run), assume empty + } + + const bunTagsToCheck = (process.env.BUN_TAGS_TO_CHECK || "canary,latest").split(","); + const distros = (process.env.DISTROS || "alpine,debian-slim,debian").split(","); + + const matrix: any[] = []; + + // Get Bun versions + const bunVersions: string[] = []; + for (const tag of bunTagsToCheck) { + const versions = await getVersions("bun", [tag]); + bunVersions.push(...versions); + } + // Unique bun versions + const uniqueBunVersions = [...new Set(bunVersions)]; + + // Filter Node versions + const nodeReleases = releases.filter((release) => [20, 22, 24, 25].includes(release?.major || 0)); + + for (const bunVersion of uniqueBunVersions) { + for (const release of nodeReleases) { + if (!release) continue; + + const currentVersion = versionsJson.nodejs?.[release.major]?.version; + const bunVersionClean = bunVersion.replace("v", ""); + const nodeVersionClean = release.versionWithPrefix.replace("v", ""); + + // Check if we need to build + // We build if: + // 1. Node version is new (different from versions.json) + // 2. Bun version is new (different from versions.json for that tag) - Wait, versions.json tracks ONE bun version per tag. + // If we have multiple bun versions (e.g. canary updates), we should probably build all combinations if they are new? + // For simplicity and matching previous logic: + // We check if the specific combination needs update? + // The previous logic was: if node updated, build all bun. If bun updated, build all node. + + // Let's simplify: We build everything that is "current" according to the inputs, + // BUT we can filter if we want. + // However, the requirement is "Make the version update of node automatic". + // So if Node updates, we build. + + // Let's stick to the plan: + // We need to know if we SHOULD build. + // If versions.json is missing, build everything. + // If versions.json exists: + // - Check if Node version is different. + // - Check if Bun version is different (we need to know which tag this bun version corresponds to, which is hard if we just have the version string). + + // Actually, the previous logic in build_updated.sh was: + // IF NODE_VERSIONS_TO_BUILD is set, build those. + // IF BUN_VERSIONS_TO_BUILD is set, build those. + + // So here, we should output the FULL matrix of what is CURRENTLY available, + // AND let the workflow filter? Or filter here? + // Filtering here is better. + + // But wait, if we use matrix, we want to output a list of jobs. + // Each job needs: bun_version, node_version, distro. + + // Let's determine what changed. + const isNodeChanged = currentVersion !== release.versionWithPrefix; + + // For Bun, we need to compare against the stored version for 'latest' or 'canary'. + // This is tricky because we iterate over resolved versions. + // Let's assume if the version string is different from what's in versions.json (values of bun object), it's new. + const storedBunVersions = Object.values(versionsJson.bun || {}); + const isBunChanged = !storedBunVersions.includes(`v${bunVersionClean}`); + + if (isNodeChanged || isBunChanged) { + for (const distro of distros) { + matrix.push({ + bun_version: bunVersionClean, + node_version: nodeVersionClean, + distro: distro + }); + } + } + } + } + + console.log(JSON.stringify({ include: matrix })); +} + const main = async () => { - if (process.argv.includes("--bun")) { - const arg = process.argv.find((a) => a.startsWith("--bun"))!; - const tagsArg = - arg.split("=")[1] ?? process.argv[process.argv.indexOf("--bun") + 1]; - const tags = (tagsArg || "latest").split(","); - const versions = await getVersions("bun", tags); - console.log(versions.join(",")); + if (process.argv.includes("--cleanup")) { + await cleanup(); + if (process.argv.includes("--generate")) { + await generateDockerfiles(); + } + return; + } + + if (process.argv.includes("--generate")) { + await generateDockerfiles(); + return; + } + + if (process.argv.includes("--matrix")) { + await generateMatrix(); return; } + // ... existing logic for --bun and --node (keep for backward compatibility or remove if unused) ... + if (process.argv.includes("--bun")) { + // ... (keep existing) + const arg = process.argv.find((a) => a.startsWith("--bun"))!; + const tagsArg = arg.split("=")[1] ?? process.argv[process.argv.indexOf("--bun") + 1]; + const tags = (tagsArg || "latest").split(","); + const versions = await getVersions("bun", tags); + console.log(versions.join(",")); + return; + } + if (process.argv.includes("--node")) { - console.log( - (await generateReleaseData()) + // ... (keep existing) + const releases = await generateReleaseData(); + const versionsJson = await Bun.file("versions.json").json(); + + const newVersions = releases .filter((release) => [20, 22, 24, 25].includes(release?.major || 0)) + .filter((release) => { + if (!release) return false; + const currentVersion = versionsJson.nodejs[release.major]?.version; + // If current version is not set, or is different (assuming we only get newer versions from nodevu), it's an update. + // Actually, nodevu returns the LATEST version for that major. + // So if versions.json has v20.10.0 and nodevu says v20.11.0, we want to build. + // If versions.json has v20.11.0 and nodevu says v20.11.0, we don't want to build. + return currentVersion !== release.versionWithPrefix; + }) .map((release) => release?.versionWithPrefix.replace("v", "")) - .join(",") - ); + .join(","); + + console.log(newVersions); } }; diff --git a/commit_changes.sh b/commit_changes.sh deleted file mode 100755 index efbd9aa..0000000 --- a/commit_changes.sh +++ /dev/null @@ -1,26 +0,0 @@ -#!/bin/bash - -set -e - -# Extract versions from versions.json -BUN_CANARY_VERSION=$(jq -r '.bun.canary' versions.json) -BUN_LATEST_VERSION=$(jq -r '.bun.latest' versions.json) -NODE_VERSIONS=$(jq -r '.nodejs | to_entries[] | "\(.value.name): \(.value.version)"' versions.json) - -# Generate the commit message -COMMIT_MESSAGE="build: update image(s) version - -- bun: (canary) ${BUN_CANARY_VERSION}, (latest) ${BUN_LATEST_VERSION} -- nodejs: -${NODE_VERSIONS} -- distro: ${DISTROS}" - -# Configure git -git config --local user.email "github-actions[bot]@users.noreply.github.com" -git config --local user.name "github-actions[bot]" - -# Add changes and commit if there are any -git add versions.json -if ! git diff-index --quiet HEAD; then - git commit -m "$COMMIT_MESSAGE" -fi diff --git a/docs/research_matrix.md b/docs/research_matrix.md new file mode 100644 index 0000000..6294574 --- /dev/null +++ b/docs/research_matrix.md @@ -0,0 +1,45 @@ +# Research: Workflow Matrix vs Single Builder + +## Context +The current build system uses a "Single Builder" pattern where a single job runs a script (`build_updated.sh`) that iterates over all versions and builds them sequentially. Issue #24 suggests investigating if a GitHub Actions Matrix strategy would be better. + +## Comparison + +| Feature | Single Builder (Current) | Workflow Matrix | +| :--- | :--- | :--- | +| **Parallelism** | Low (Sequential builds in loop) | High (Concurrent jobs) | +| **Failures** | One failure can stop the whole loop (unless handled carefully) | Isolated failures per job | +| **Resource Usage** | Single runner, longer duration | Multiple runners, shorter duration (burst) | +| **Complexity** | High (Bash script logic) | Medium (YAML configuration) | +| **Cost** | Lower (1 runner time) | Higher (Multiple runners init time overhead) | +| **Logs** | Mixed in one huge log | Separated per job | +| **Dynamic** | Hard (Need to generate JSON for matrix) | Native support for dynamic matrix | + +## Analysis for bun-node + +### Pros of Matrix +1. **Speed:** Building multiple Docker images is time-consuming. Parallelizing this would significantly reduce total build time. +2. **Isolation:** If one image build fails (e.g., specific Node version issue), it won't block others. +3. **Clarity:** GitHub UI shows exactly which build failed. + +### Cons of Matrix +1. **Complexity of Dynamic Matrix:** Since we only want to build *updated* versions, we need a "setup" job that calculates the matrix (JSON) and passes it to the build job. +2. **Concurrency Limits:** GitHub Free tier has concurrency limits (20 jobs). If we have many versions, we might queue. +3. **Shared State:** The current script updates `versions.json` after each build. In a matrix, we'd need to aggregate these updates or handle them differently (e.g., one final commit job). + +## Recommendation +**Migrate to Matrix.** +The benefits of parallelism and isolation outweigh the setup complexity. +The `versions.json` update issue (Issue 35) actually helps here: if we move `versions.json` to Releases, we don't need to commit to git from every job. We can have a final "Release" job that aggregates the successful builds and updates the `versions.json` in the Release assets. + +## Implementation Strategy +1. **Job 1: Check Versions** + - Runs `check-bun-node.ts`. + - Outputs a JSON matrix of versions to build. +2. **Job 2: Build (Matrix)** + - `needs: [check-versions]` + - `strategy: matrix: ${{ fromJson(needs.check-versions.outputs.matrix) }}` + - Builds and pushes single image. +3. **Job 3: Update Release** + - `needs: [build]` + - Updates `versions.json` and uploads to GitHub Release. diff --git a/src/base/20/alpine/dockerfile b/src/base/20/alpine/dockerfile index 79d00a0..d336d96 100644 --- a/src/base/20/alpine/dockerfile +++ b/src/base/20/alpine/dockerfile @@ -1,4 +1,4 @@ -FROM alpine:3.20 AS build +FROM alpine:3.22 AS build # https://github.com/oven-sh/bun/releases ARG BUN_VERSION=latest @@ -44,7 +44,7 @@ RUN apk --no-cache add ca-certificates curl dirmngr gpg gpg-agent unzip \ && rm -f "bun-linux-$build.zip" SHASUMS256.txt.asc SHASUMS256.txt \ && chmod +x /usr/local/bin/bun -FROM node:20-alpine3.20 +FROM node:20-alpine3.22 # Disable the runtime transpiler cache by default inside Docker containers. # On ephemeral containers, the cache is not useful diff --git a/src/base/22/alpine/dockerfile b/src/base/22/alpine/dockerfile index cf5a945..0222cc7 100644 --- a/src/base/22/alpine/dockerfile +++ b/src/base/22/alpine/dockerfile @@ -1,4 +1,4 @@ -FROM alpine:3.20 AS build +FROM alpine:3.22 AS build # https://github.com/oven-sh/bun/releases ARG BUN_VERSION=latest @@ -44,7 +44,7 @@ RUN apk --no-cache add ca-certificates curl dirmngr gpg gpg-agent unzip \ && rm -f "bun-linux-$build.zip" SHASUMS256.txt.asc SHASUMS256.txt \ && chmod +x /usr/local/bin/bun -FROM node:22-alpine3.20 +FROM node:22-alpine3.22 # Disable the runtime transpiler cache by default inside Docker containers. # On ephemeral containers, the cache is not useful diff --git a/src/base/24/alpine/dockerfile b/src/base/24/alpine/dockerfile index 4f7824e..812d3a9 100644 --- a/src/base/24/alpine/dockerfile +++ b/src/base/24/alpine/dockerfile @@ -1,4 +1,4 @@ -FROM alpine:3.20 AS build +FROM alpine:3.22 AS build # https://github.com/oven-sh/bun/releases ARG BUN_VERSION=latest @@ -44,7 +44,7 @@ RUN apk --no-cache add ca-certificates curl dirmngr gpg gpg-agent unzip \ && rm -f "bun-linux-$build.zip" SHASUMS256.txt.asc SHASUMS256.txt \ && chmod +x /usr/local/bin/bun -FROM node:24-alpine3.20 +FROM node:24-alpine3.22 # Disable the runtime transpiler cache by default inside Docker containers. # On ephemeral containers, the cache is not useful @@ -57,6 +57,8 @@ ENV BUN_INSTALL_BIN=${BUN_INSTALL_BIN} COPY --from=build /usr/local/bin/bun /usr/local/bin/ COPY docker-entrypoint.sh /usr/local/bin/ +RUN mkdir -p /usr/local/bun-node-fallback-bin && ln -s /usr/local/bin/bun /usr/local/bun-node-fallback-bin/node +ENV PATH "${PATH}:/usr/local/bun-node-fallback-bin" # Temporarily use the `build`-stage /tmp folder to access the glibc APKs: RUN --mount=type=bind,from=build,source=/tmp,target=/tmp \ diff --git a/src/base/24/debian-slim/dockerfile b/src/base/24/debian-slim/dockerfile index 99dca2c..19be811 100644 --- a/src/base/24/debian-slim/dockerfile +++ b/src/base/24/debian-slim/dockerfile @@ -1,4 +1,4 @@ -FROM debian:bullseye-slim AS build +FROM debian:bookworm-slim AS build # https://github.com/oven-sh/bun/releases ARG BUN_VERSION=latest @@ -55,7 +55,7 @@ RUN apt-get update -qq \ && which bun \ && bun --version -FROM node:24-bullseye-slim +FROM node:24-bookworm-slim # Disable the runtime transpiler cache by default inside Docker containers. # On ephemeral containers, the cache is not useful @@ -68,6 +68,8 @@ ENV BUN_INSTALL_BIN=${BUN_INSTALL_BIN} COPY docker-entrypoint.sh /usr/local/bin COPY --from=build /usr/local/bin/bun /usr/local/bin/bun +RUN mkdir -p /usr/local/bun-node-fallback-bin && ln -s /usr/local/bin/bun /usr/local/bun-node-fallback-bin/node +ENV PATH "${PATH}:/usr/local/bun-node-fallback-bin" RUN groupadd bun \ --gid 1001 \ diff --git a/src/base/24/debian/dockerfile b/src/base/24/debian/dockerfile index 9b5b00f..9e98d62 100644 --- a/src/base/24/debian/dockerfile +++ b/src/base/24/debian/dockerfile @@ -1,4 +1,4 @@ -FROM debian:bullseye-slim AS build +FROM debian:bookworm-slim AS build # https://github.com/oven-sh/bun/releases ARG BUN_VERSION=latest @@ -56,10 +56,12 @@ RUN apt-get update -qq \ && rm -f "bun-linux-$build.zip" SHASUMS256.txt.asc SHASUMS256.txt \ && chmod +x /usr/local/bin/bun -FROM node:24-bullseye +FROM node:24-bookworm COPY docker-entrypoint.sh /usr/local/bin COPY --from=build /usr/local/bin/bun /usr/local/bin/bun +RUN mkdir -p /usr/local/bun-node-fallback-bin && ln -s /usr/local/bin/bun /usr/local/bun-node-fallback-bin/node +ENV PATH "${PATH}:/usr/local/bun-node-fallback-bin" # Disable the runtime transpiler cache by default inside Docker containers. # On ephemeral containers, the cache is not useful diff --git a/src/base/25/alpine/dockerfile b/src/base/25/alpine/dockerfile index 07f60ea..d094a1d 100644 --- a/src/base/25/alpine/dockerfile +++ b/src/base/25/alpine/dockerfile @@ -1,4 +1,4 @@ -FROM alpine:3.20 AS build +FROM alpine:3.22 AS build # https://github.com/oven-sh/bun/releases ARG BUN_VERSION=latest @@ -57,6 +57,8 @@ ENV BUN_INSTALL_BIN=${BUN_INSTALL_BIN} COPY --from=build /usr/local/bin/bun /usr/local/bin/ COPY docker-entrypoint.sh /usr/local/bin/ +RUN mkdir -p /usr/local/bun-node-fallback-bin && ln -s /usr/local/bin/bun /usr/local/bun-node-fallback-bin/node +ENV PATH "${PATH}:/usr/local/bun-node-fallback-bin" # Temporarily use the `build`-stage /tmp folder to access the glibc APKs: RUN --mount=type=bind,from=build,source=/tmp,target=/tmp \ diff --git a/src/base/25/debian-slim/dockerfile b/src/base/25/debian-slim/dockerfile index a0505c4..29664de 100644 --- a/src/base/25/debian-slim/dockerfile +++ b/src/base/25/debian-slim/dockerfile @@ -1,4 +1,4 @@ -FROM debian:bullseye-slim AS build +FROM debian:bookworm-slim AS build # https://github.com/oven-sh/bun/releases ARG BUN_VERSION=latest @@ -55,7 +55,7 @@ RUN apt-get update -qq \ && which bun \ && bun --version -FROM node:25-bullseye-slim +FROM node:25-bookworm-slim # Disable the runtime transpiler cache by default inside Docker containers. # On ephemeral containers, the cache is not useful @@ -68,6 +68,8 @@ ENV BUN_INSTALL_BIN=${BUN_INSTALL_BIN} COPY docker-entrypoint.sh /usr/local/bin COPY --from=build /usr/local/bin/bun /usr/local/bin/bun +RUN mkdir -p /usr/local/bun-node-fallback-bin && ln -s /usr/local/bin/bun /usr/local/bun-node-fallback-bin/node +ENV PATH "${PATH}:/usr/local/bun-node-fallback-bin" RUN groupadd bun \ --gid 1001 \ diff --git a/src/base/25/debian/dockerfile b/src/base/25/debian/dockerfile index b9a21ef..a10d6ca 100644 --- a/src/base/25/debian/dockerfile +++ b/src/base/25/debian/dockerfile @@ -1,4 +1,4 @@ -FROM debian:bullseye-slim AS build +FROM debian:bookworm-slim AS build # https://github.com/oven-sh/bun/releases ARG BUN_VERSION=latest @@ -56,10 +56,12 @@ RUN apt-get update -qq \ && rm -f "bun-linux-$build.zip" SHASUMS256.txt.asc SHASUMS256.txt \ && chmod +x /usr/local/bin/bun -FROM node:25-bullseye +FROM node:25-bookworm COPY docker-entrypoint.sh /usr/local/bin COPY --from=build /usr/local/bin/bun /usr/local/bin/bun +RUN mkdir -p /usr/local/bun-node-fallback-bin && ln -s /usr/local/bin/bun /usr/local/bun-node-fallback-bin/node +ENV PATH "${PATH}:/usr/local/bun-node-fallback-bin" # Disable the runtime transpiler cache by default inside Docker containers. # On ephemeral containers, the cache is not useful diff --git a/src/git/20/alpine/dockerfile b/src/git/20/alpine/dockerfile index ae371ee..f0d70e9 100644 --- a/src/git/20/alpine/dockerfile +++ b/src/git/20/alpine/dockerfile @@ -1,4 +1,4 @@ -FROM alpine:3.20 AS build +FROM alpine:3.22 AS build # https://github.com/oven-sh/bun/releases ARG BUN_VERSION=latest @@ -44,7 +44,7 @@ RUN apk --no-cache add ca-certificates curl dirmngr gpg gpg-agent unzip \ && rm -f "bun-linux-$build.zip" SHASUMS256.txt.asc SHASUMS256.txt \ && chmod +x /usr/local/bin/bun -FROM node:20-alpine3.20 +FROM node:20-alpine3.22 # Disable the runtime transpiler cache by default inside Docker containers. # On ephemeral containers, the cache is not useful diff --git a/src/git/22/alpine/dockerfile b/src/git/22/alpine/dockerfile index a3a9b2e..be3c92b 100644 --- a/src/git/22/alpine/dockerfile +++ b/src/git/22/alpine/dockerfile @@ -1,4 +1,4 @@ -FROM alpine:3.20 AS build +FROM alpine:3.22 AS build # https://github.com/oven-sh/bun/releases ARG BUN_VERSION=latest @@ -44,7 +44,7 @@ RUN apk --no-cache add ca-certificates curl dirmngr gpg gpg-agent unzip \ && rm -f "bun-linux-$build.zip" SHASUMS256.txt.asc SHASUMS256.txt \ && chmod +x /usr/local/bin/bun -FROM node:22-alpine3.20 +FROM node:22-alpine3.22 # Disable the runtime transpiler cache by default inside Docker containers. # On ephemeral containers, the cache is not useful diff --git a/src/git/24/alpine/dockerfile b/src/git/24/alpine/dockerfile index d844929..1f2744b 100644 --- a/src/git/24/alpine/dockerfile +++ b/src/git/24/alpine/dockerfile @@ -1,4 +1,4 @@ -FROM alpine:3.20 AS build +FROM alpine:3.22 AS build # https://github.com/oven-sh/bun/releases ARG BUN_VERSION=latest @@ -44,7 +44,7 @@ RUN apk --no-cache add ca-certificates curl dirmngr gpg gpg-agent unzip \ && rm -f "bun-linux-$build.zip" SHASUMS256.txt.asc SHASUMS256.txt \ && chmod +x /usr/local/bin/bun -FROM node:24-alpine3.20 +FROM node:24-alpine3.22 # Disable the runtime transpiler cache by default inside Docker containers. # On ephemeral containers, the cache is not useful @@ -57,6 +57,8 @@ ENV BUN_INSTALL_BIN=${BUN_INSTALL_BIN} COPY --from=build /usr/local/bin/bun /usr/local/bin/ COPY docker-entrypoint.sh /usr/local/bin/ +RUN mkdir -p /usr/local/bun-node-fallback-bin && ln -s /usr/local/bin/bun /usr/local/bun-node-fallback-bin/node +ENV PATH "${PATH}:/usr/local/bun-node-fallback-bin" # Temporarily use the `build`-stage /tmp folder to access the glibc APKs: RUN --mount=type=bind,from=build,source=/tmp,target=/tmp \ diff --git a/src/git/25/alpine/dockerfile b/src/git/25/alpine/dockerfile index 167ff10..49a21f0 100644 --- a/src/git/25/alpine/dockerfile +++ b/src/git/25/alpine/dockerfile @@ -1,4 +1,4 @@ -FROM alpine:3.20 AS build +FROM alpine:3.22 AS build # https://github.com/oven-sh/bun/releases ARG BUN_VERSION=latest @@ -57,6 +57,8 @@ ENV BUN_INSTALL_BIN=${BUN_INSTALL_BIN} COPY --from=build /usr/local/bin/bun /usr/local/bin/ COPY docker-entrypoint.sh /usr/local/bin/ +RUN mkdir -p /usr/local/bun-node-fallback-bin && ln -s /usr/local/bin/bun /usr/local/bun-node-fallback-bin/node +ENV PATH "${PATH}:/usr/local/bun-node-fallback-bin" # Temporarily use the `build`-stage /tmp folder to access the glibc APKs: RUN --mount=type=bind,from=build,source=/tmp,target=/tmp \ diff --git a/versions.json b/versions.json deleted file mode 100644 index 3eb5740..0000000 --- a/versions.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "bun": { - "latest": "v1.3.3", - "canary": "v1.3.2-canary.20251121.1" - }, - "nodejs": { - "25": { - "name": "current", - "version": "v25.2.1" - }, - "24": { - "name": "krypton", - "version": "v24.11.1" - }, - "22": { - "name": "jod", - "version": "v22.21.1" - }, - "20": { - "name": "iron", - "version": "v20.19.5" - } - } -}