Repository navigation
Keep DockerHub optional and report only successfully published tags #106
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Build and Publish | |
| on: | |
| schedule: | |
| # At 04:00 on day-of-month 1. | |
| - cron: "0 4 1 * *" | |
| workflow_dispatch: | |
| push: | |
| branches: | |
| - "main" | |
| permissions: | |
| contents: read | |
| env: | |
| DOCKERHUB_IMAGE: betterweb/node | |
| FORGEJO_IMAGE: code.bettercorp.dev/bettercorp/docker-node | |
| jobs: | |
| prepare-matrix: | |
| runs-on: blacksmith-2vcpu-ubuntu-2404 | |
| outputs: | |
| matrix: ${{ steps.set-matrix.outputs.matrix }} | |
| latest: ${{ steps.set-matrix.outputs.latest }} | |
| devcontainer_matrix: ${{ steps.set-matrix.outputs.matrix }} | |
| steps: | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@v4 | |
| - uses: actions/checkout@v6 | |
| - name: Select published supported Node images | |
| id: set-matrix | |
| run: scripts/prepare-matrix.sh >> "$GITHUB_OUTPUT" | |
| build: | |
| needs: prepare-matrix | |
| runs-on: blacksmith-2vcpu-ubuntu-2404 | |
| strategy: | |
| fail-fast: false | |
| matrix: ${{fromJson(needs.prepare-matrix.outputs.matrix)}} | |
| steps: | |
| - uses: actions/checkout@v6 | |
| with: | |
| fetch-depth: 0 | |
| - name: Set up QEMU | |
| uses: docker/setup-qemu-action@v4 | |
| with: | |
| platforms: all | |
| - name: Set up Docker Buildx | |
| id: buildx | |
| uses: docker/setup-buildx-action@v4 | |
| - name: Build and test runtime permissions | |
| run: | | |
| NODE_VERSION=${{ matrix.tag.version }} | |
| docker build --build-arg NODE_VERSION="${NODE_VERSION#v}" -t betterweb/node:permission-test . | |
| tests/permissions.sh betterweb/node:permission-test | |
| - name: Login to Forgejo | |
| uses: docker/login-action@v4 | |
| with: | |
| registry: code.bettercorp.dev | |
| username: ${{ secrets.BC_CODE_USER }} | |
| password: ${{ secrets.BC_CODE_PACKAGE_TOKEN }} | |
| logout: true | |
| - name: Parse version | |
| id: parse_version | |
| run: | | |
| VERSION=${{ matrix.tag.version }} | |
| PARSED_VERSION=${VERSION#v} | |
| echo "PARSED_VERSION=$PARSED_VERSION" >> $GITHUB_OUTPUT | |
| shell: bash | |
| - name: Is Latest | |
| id: is_latest | |
| run: | | |
| if [ "${{ matrix.tag.version }}" = "${{ needs.prepare-matrix.outputs.latest }}" ]; then | |
| echo "IS_LATEST=true" >> $GITHUB_OUTPUT | |
| echo "This is the latest version" | |
| else | |
| echo "IS_LATEST=false" >> $GITHUB_OUTPUT | |
| echo "This is not the latest version" | |
| fi | |
| - name: Build and push to Forgejo | |
| id: build_image | |
| uses: docker/build-push-action@v7 | |
| with: | |
| build-args: | | |
| NODE_MAJOR=${{ matrix.tag.major }} | |
| NODE_VERSION=${{ steps.parse_version.outputs.PARSED_VERSION }} | |
| context: . | |
| platforms: ${{ matrix.tag.platforms }} | |
| push: true | |
| tags: | | |
| ${{ env.FORGEJO_IMAGE }}:${{steps.parse_version.outputs.PARSED_VERSION}} | |
| ${{ env.FORGEJO_IMAGE }}:${{matrix.tag.major}} | |
| ${{ steps.is_latest.outputs.IS_LATEST == 'true' && format('{0}:latest', env.FORGEJO_IMAGE) || '' }} | |
| - name: Login to DockerHub | |
| id: dockerhub_login | |
| continue-on-error: true | |
| uses: docker/login-action@v4 | |
| with: | |
| username: ${{ secrets.DOCKER_HUB_USER }} | |
| password: ${{ secrets.DOCKER_HUB_TOKEN }} | |
| logout: true | |
| - name: Mirror the built image to DockerHub | |
| id: mirror | |
| if: steps.dockerhub_login.outcome == 'success' | |
| continue-on-error: true | |
| run: | | |
| tags=(--tag "${{ env.DOCKERHUB_IMAGE }}:${{ steps.parse_version.outputs.PARSED_VERSION }}" | |
| --tag "${{ env.DOCKERHUB_IMAGE }}:${{ matrix.tag.major }}") | |
| if [ "${{ steps.is_latest.outputs.IS_LATEST }}" = true ]; then | |
| tags+=(--tag "${{ env.DOCKERHUB_IMAGE }}:latest") | |
| fi | |
| docker buildx imagetools create "${tags[@]}" "${{ env.FORGEJO_IMAGE }}@${{ steps.build_image.outputs.digest }}" | |
| - name: Write manifest part | |
| run: | | |
| TAGS=$(jq -nc \ | |
| --arg v "${{ steps.parse_version.outputs.PARSED_VERSION }}" \ | |
| --arg m "${{ matrix.tag.major }}" \ | |
| --arg latest "${{ steps.is_latest.outputs.IS_LATEST }}" \ | |
| --arg dockerhub "${{ env.DOCKERHUB_IMAGE }}" \ | |
| --argjson mirrored "${{ steps.mirror.outcome == 'success' }}" \ | |
| --arg forgejo "${{ env.FORGEJO_IMAGE }}" \ | |
| '[($dockerhub + ":" + $v), ($dockerhub + ":" + $m), ($forgejo + ":" + $v), ($forgejo + ":" + $m)] + (if $latest == "true" then [($dockerhub + ":latest"), ($forgejo + ":latest")] else [] end) | if $mirrored then . else map(select(startswith($dockerhub + ":") | not)) end') | |
| PLATFORMS=$(echo "${{ matrix.tag.platforms }}" | jq -Rc 'split(",")') | |
| jq -n \ | |
| --arg version "${{ steps.parse_version.outputs.PARSED_VERSION }}" \ | |
| --argjson major ${{ matrix.tag.major }} \ | |
| --arg variant "base" \ | |
| --argjson platforms "$PLATFORMS" \ | |
| --argjson tags "$TAGS" \ | |
| --arg digest "${{ steps.build_image.outputs.digest }}" \ | |
| --argjson is_latest ${{ steps.is_latest.outputs.IS_LATEST }} \ | |
| '{version:$version, major:$major, variant:$variant, platforms:$platforms, tags:$tags, digest:$digest, is_latest:$is_latest}' \ | |
| > part.json | |
| cat part.json | |
| - uses: actions/upload-artifact@v4 | |
| with: | |
| name: manifest-part-base-${{ matrix.tag.major }} | |
| path: part.json | |
| build-dev: | |
| needs: [prepare-matrix, build] | |
| runs-on: blacksmith-2vcpu-ubuntu-2404 | |
| strategy: | |
| fail-fast: false | |
| matrix: ${{fromJson(needs.prepare-matrix.outputs.devcontainer_matrix)}} | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@v4 | |
| - name: Login to Forgejo | |
| uses: docker/login-action@v4 | |
| with: | |
| registry: code.bettercorp.dev | |
| username: ${{ secrets.BC_CODE_USER }} | |
| password: ${{ secrets.BC_CODE_PACKAGE_TOKEN }} | |
| logout: true | |
| - name: Parse version | |
| id: parse_version | |
| run: | | |
| VERSION=${{ matrix.tag.version }} | |
| PARSED_VERSION=${VERSION#v} | |
| echo "PARSED_VERSION=$PARSED_VERSION" >> $GITHUB_OUTPUT | |
| shell: bash | |
| - name: Is Latest | |
| id: is_latest | |
| run: | | |
| if [ "${{ matrix.tag.version }}" = "${{ needs.prepare-matrix.outputs.latest }}" ]; then | |
| echo "IS_LATEST=true" >> $GITHUB_OUTPUT | |
| echo "This is the latest version" | |
| else | |
| echo "IS_LATEST=false" >> $GITHUB_OUTPUT | |
| echo "This is not the latest version" | |
| fi | |
| - name: Build and push dev image to Forgejo | |
| id: build_image | |
| uses: docker/build-push-action@v7 | |
| with: | |
| context: . | |
| file: Dockerfile.dev | |
| push: true | |
| build-args: | | |
| BASE_IMAGE=${{ env.FORGEJO_IMAGE }} | |
| NODE_MAJOR=${{ matrix.tag.major }} | |
| NODE_VERSION=${{ steps.parse_version.outputs.PARSED_VERSION }} | |
| tags: | | |
| ${{ env.FORGEJO_IMAGE }}:dev-${{steps.parse_version.outputs.PARSED_VERSION}} | |
| ${{ env.FORGEJO_IMAGE }}:dev-${{matrix.tag.major}} | |
| ${{ steps.is_latest.outputs.IS_LATEST == 'true' && format('{0}:dev', env.FORGEJO_IMAGE) || '' }} | |
| - name: Login to DockerHub | |
| id: dockerhub_login | |
| continue-on-error: true | |
| uses: docker/login-action@v4 | |
| with: | |
| username: ${{ secrets.DOCKER_HUB_USER }} | |
| password: ${{ secrets.DOCKER_HUB_TOKEN }} | |
| logout: true | |
| - name: Mirror the built image to DockerHub | |
| id: mirror | |
| if: steps.dockerhub_login.outcome == 'success' | |
| continue-on-error: true | |
| run: | | |
| tags=(--tag "${{ env.DOCKERHUB_IMAGE }}:dev-${{ steps.parse_version.outputs.PARSED_VERSION }}" | |
| --tag "${{ env.DOCKERHUB_IMAGE }}:dev-${{ matrix.tag.major }}") | |
| if [ "${{ steps.is_latest.outputs.IS_LATEST }}" = true ]; then | |
| tags+=(--tag "${{ env.DOCKERHUB_IMAGE }}:dev") | |
| fi | |
| docker buildx imagetools create "${tags[@]}" "${{ env.FORGEJO_IMAGE }}@${{ steps.build_image.outputs.digest }}" | |
| - name: Write manifest part | |
| run: | | |
| TAGS=$(jq -nc \ | |
| --arg v "${{ steps.parse_version.outputs.PARSED_VERSION }}" \ | |
| --arg m "${{ matrix.tag.major }}" \ | |
| --arg latest "${{ steps.is_latest.outputs.IS_LATEST }}" \ | |
| --arg dockerhub "${{ env.DOCKERHUB_IMAGE }}" \ | |
| --argjson mirrored "${{ steps.mirror.outcome == 'success' }}" \ | |
| --arg forgejo "${{ env.FORGEJO_IMAGE }}" \ | |
| '[($dockerhub + ":dev-" + $v), ($dockerhub + ":dev-" + $m), ($forgejo + ":dev-" + $v), ($forgejo + ":dev-" + $m)] + (if $latest == "true" then [($dockerhub + ":dev"), ($forgejo + ":dev")] else [] end) | if $mirrored then . else map(select(startswith($dockerhub + ":") | not)) end') | |
| jq -n \ | |
| --arg version "${{ steps.parse_version.outputs.PARSED_VERSION }}" \ | |
| --argjson major ${{ matrix.tag.major }} \ | |
| --arg variant "dev" \ | |
| --argjson platforms '["linux/amd64"]' \ | |
| --argjson tags "$TAGS" \ | |
| --arg digest "${{ steps.build_image.outputs.digest }}" \ | |
| --argjson is_latest ${{ steps.is_latest.outputs.IS_LATEST }} \ | |
| '{version:$version, major:$major, variant:$variant, platforms:$platforms, tags:$tags, digest:$digest, is_latest:$is_latest}' \ | |
| > part.json | |
| cat part.json | |
| - uses: actions/upload-artifact@v4 | |
| with: | |
| name: manifest-part-dev-${{ matrix.tag.major }} | |
| path: part.json | |
| build-devcontainers: | |
| needs: prepare-matrix | |
| runs-on: blacksmith-2vcpu-ubuntu-2404 | |
| strategy: | |
| fail-fast: false | |
| matrix: ${{fromJson(needs.prepare-matrix.outputs.devcontainer_matrix)}} | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@v4 | |
| - name: Login to Forgejo | |
| uses: docker/login-action@v4 | |
| with: | |
| registry: code.bettercorp.dev | |
| username: ${{ secrets.BC_CODE_USER }} | |
| password: ${{ secrets.BC_CODE_PACKAGE_TOKEN }} | |
| logout: true | |
| - name: Parse version | |
| id: parse_version | |
| run: | | |
| VERSION=${{ matrix.tag.version }} | |
| PARSED_VERSION=${VERSION#v} | |
| echo "PARSED_VERSION=$PARSED_VERSION" >> $GITHUB_OUTPUT | |
| shell: bash | |
| - name: Is Latest | |
| id: is_latest | |
| run: | | |
| if [ "${{ matrix.tag.version }}" = "${{ needs.prepare-matrix.outputs.latest }}" ]; then | |
| echo "IS_LATEST=true" >> $GITHUB_OUTPUT | |
| echo "This is the latest version" | |
| else | |
| echo "IS_LATEST=false" >> $GITHUB_OUTPUT | |
| echo "This is not the latest version" | |
| fi | |
| - name: Build and push devcontainer image to Forgejo | |
| id: build_image | |
| uses: docker/build-push-action@v7 | |
| with: | |
| context: . | |
| file: Dockerfile.devcontainer | |
| push: true | |
| build-args: | | |
| NODE_MAJOR=${{ matrix.tag.major }} | |
| NODE_VERSION=${{ steps.parse_version.outputs.PARSED_VERSION }} | |
| tags: | | |
| ${{ env.FORGEJO_IMAGE }}:devcontainer-${{steps.parse_version.outputs.PARSED_VERSION}} | |
| ${{ env.FORGEJO_IMAGE }}:devcontainer-${{matrix.tag.major}} | |
| ${{ steps.is_latest.outputs.IS_LATEST == 'true' && format('{0}:devcontainer-latest', env.FORGEJO_IMAGE) || '' }} | |
| - name: Login to DockerHub | |
| id: dockerhub_login | |
| continue-on-error: true | |
| uses: docker/login-action@v4 | |
| with: | |
| username: ${{ secrets.DOCKER_HUB_USER }} | |
| password: ${{ secrets.DOCKER_HUB_TOKEN }} | |
| logout: true | |
| - name: Mirror the built image to DockerHub | |
| id: mirror | |
| if: steps.dockerhub_login.outcome == 'success' | |
| continue-on-error: true | |
| run: | | |
| tags=(--tag "${{ env.DOCKERHUB_IMAGE }}:devcontainer-${{ steps.parse_version.outputs.PARSED_VERSION }}" | |
| --tag "${{ env.DOCKERHUB_IMAGE }}:devcontainer-${{ matrix.tag.major }}") | |
| if [ "${{ steps.is_latest.outputs.IS_LATEST }}" = true ]; then | |
| tags+=(--tag "${{ env.DOCKERHUB_IMAGE }}:devcontainer-latest") | |
| fi | |
| docker buildx imagetools create "${tags[@]}" "${{ env.FORGEJO_IMAGE }}@${{ steps.build_image.outputs.digest }}" | |
| - name: Write manifest part | |
| run: | | |
| TAGS=$(jq -nc \ | |
| --arg v "${{ steps.parse_version.outputs.PARSED_VERSION }}" \ | |
| --arg m "${{ matrix.tag.major }}" \ | |
| --arg latest "${{ steps.is_latest.outputs.IS_LATEST }}" \ | |
| --arg dockerhub "${{ env.DOCKERHUB_IMAGE }}" \ | |
| --argjson mirrored "${{ steps.mirror.outcome == 'success' }}" \ | |
| --arg forgejo "${{ env.FORGEJO_IMAGE }}" \ | |
| '[($dockerhub + ":devcontainer-" + $v), ($dockerhub + ":devcontainer-" + $m), ($forgejo + ":devcontainer-" + $v), ($forgejo + ":devcontainer-" + $m)] + (if $latest == "true" then [($dockerhub + ":devcontainer-latest"), ($forgejo + ":devcontainer-latest")] else [] end) | if $mirrored then . else map(select(startswith($dockerhub + ":") | not)) end') | |
| jq -n \ | |
| --arg version "${{ steps.parse_version.outputs.PARSED_VERSION }}" \ | |
| --argjson major ${{ matrix.tag.major }} \ | |
| --arg variant "devcontainer" \ | |
| --argjson platforms '["linux/amd64"]' \ | |
| --argjson tags "$TAGS" \ | |
| --arg digest "${{ steps.build_image.outputs.digest }}" \ | |
| --argjson is_latest ${{ steps.is_latest.outputs.IS_LATEST }} \ | |
| '{version:$version, major:$major, variant:$variant, platforms:$platforms, tags:$tags, digest:$digest, is_latest:$is_latest}' \ | |
| > part.json | |
| cat part.json | |
| - uses: actions/upload-artifact@v4 | |
| with: | |
| name: manifest-part-devcontainer-${{ matrix.tag.major }} | |
| path: part.json | |
| build-dind: | |
| needs: [prepare-matrix, build] | |
| runs-on: blacksmith-2vcpu-ubuntu-2404 | |
| strategy: | |
| fail-fast: false | |
| matrix: ${{fromJson(needs.prepare-matrix.outputs.devcontainer_matrix)}} | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@v4 | |
| - name: Login to Forgejo | |
| uses: docker/login-action@v4 | |
| with: | |
| registry: code.bettercorp.dev | |
| username: ${{ secrets.BC_CODE_USER }} | |
| password: ${{ secrets.BC_CODE_PACKAGE_TOKEN }} | |
| logout: true | |
| - name: Parse version | |
| id: parse_version | |
| run: | | |
| VERSION=${{ matrix.tag.version }} | |
| PARSED_VERSION=${VERSION#v} | |
| echo "PARSED_VERSION=$PARSED_VERSION" >> $GITHUB_OUTPUT | |
| shell: bash | |
| - name: Is Latest | |
| id: is_latest | |
| run: | | |
| if [ "${{ matrix.tag.version }}" = "${{ needs.prepare-matrix.outputs.latest }}" ]; then | |
| echo "IS_LATEST=true" >> $GITHUB_OUTPUT | |
| echo "This is the latest version" | |
| else | |
| echo "IS_LATEST=false" >> $GITHUB_OUTPUT | |
| echo "This is not the latest version" | |
| fi | |
| - name: Build and push dind image to Forgejo | |
| id: build_image | |
| uses: docker/build-push-action@v7 | |
| with: | |
| context: . | |
| file: Dockerfile.dind | |
| push: true | |
| build-args: | | |
| BASE_IMAGE=${{ env.FORGEJO_IMAGE }} | |
| NODE_MAJOR=${{ matrix.tag.major }} | |
| NODE_VERSION=${{ steps.parse_version.outputs.PARSED_VERSION }} | |
| tags: | | |
| ${{ env.FORGEJO_IMAGE }}:dind-${{steps.parse_version.outputs.PARSED_VERSION}} | |
| ${{ env.FORGEJO_IMAGE }}:dind-${{matrix.tag.major}} | |
| ${{ steps.is_latest.outputs.IS_LATEST == 'true' && format('{0}:dind-latest', env.FORGEJO_IMAGE) || '' }} | |
| - name: Login to DockerHub | |
| id: dockerhub_login | |
| continue-on-error: true | |
| uses: docker/login-action@v4 | |
| with: | |
| username: ${{ secrets.DOCKER_HUB_USER }} | |
| password: ${{ secrets.DOCKER_HUB_TOKEN }} | |
| logout: true | |
| - name: Mirror the built image to DockerHub | |
| id: mirror | |
| if: steps.dockerhub_login.outcome == 'success' | |
| continue-on-error: true | |
| run: | | |
| tags=(--tag "${{ env.DOCKERHUB_IMAGE }}:dind-${{ steps.parse_version.outputs.PARSED_VERSION }}" | |
| --tag "${{ env.DOCKERHUB_IMAGE }}:dind-${{ matrix.tag.major }}") | |
| if [ "${{ steps.is_latest.outputs.IS_LATEST }}" = true ]; then | |
| tags+=(--tag "${{ env.DOCKERHUB_IMAGE }}:dind-latest") | |
| fi | |
| docker buildx imagetools create "${tags[@]}" "${{ env.FORGEJO_IMAGE }}@${{ steps.build_image.outputs.digest }}" | |
| - name: Write manifest part | |
| run: | | |
| TAGS=$(jq -nc \ | |
| --arg v "${{ steps.parse_version.outputs.PARSED_VERSION }}" \ | |
| --arg m "${{ matrix.tag.major }}" \ | |
| --arg latest "${{ steps.is_latest.outputs.IS_LATEST }}" \ | |
| --arg dockerhub "${{ env.DOCKERHUB_IMAGE }}" \ | |
| --argjson mirrored "${{ steps.mirror.outcome == 'success' }}" \ | |
| --arg forgejo "${{ env.FORGEJO_IMAGE }}" \ | |
| '[($dockerhub + ":dind-" + $v), ($dockerhub + ":dind-" + $m), ($forgejo + ":dind-" + $v), ($forgejo + ":dind-" + $m)] + (if $latest == "true" then [($dockerhub + ":dind-latest"), ($forgejo + ":dind-latest")] else [] end) | if $mirrored then . else map(select(startswith($dockerhub + ":") | not)) end') | |
| jq -n \ | |
| --arg version "${{ steps.parse_version.outputs.PARSED_VERSION }}" \ | |
| --argjson major ${{ matrix.tag.major }} \ | |
| --arg variant "dind" \ | |
| --argjson platforms '["linux/amd64"]' \ | |
| --argjson tags "$TAGS" \ | |
| --arg digest "${{ steps.build_image.outputs.digest }}" \ | |
| --argjson is_latest ${{ steps.is_latest.outputs.IS_LATEST }} \ | |
| '{version:$version, major:$major, variant:$variant, platforms:$platforms, tags:$tags, digest:$digest, is_latest:$is_latest}' \ | |
| > part.json | |
| cat part.json | |
| - uses: actions/upload-artifact@v4 | |
| with: | |
| name: manifest-part-dind-${{ matrix.tag.major }} | |
| path: part.json | |
| publish-manifest: | |
| needs: [prepare-matrix, build, build-dev, build-devcontainers, build-dind] | |
| runs-on: blacksmith-2vcpu-ubuntu-2404 | |
| steps: | |
| - uses: actions/download-artifact@v4 | |
| with: | |
| pattern: manifest-part-* | |
| merge-multiple: false | |
| path: parts/ | |
| - name: Merge manifest parts | |
| run: | | |
| TIMESTAMP=$(date -u +%Y-%m-%dT%H:%M:%SZ) | |
| LATEST_VERSION="${{ needs.prepare-matrix.outputs.latest }}" | |
| LATEST_VERSION_STRIPPED="${LATEST_VERSION#v}" | |
| jq -s \ | |
| --arg ts "$TIMESTAMP" \ | |
| --arg latest "$LATEST_VERSION_STRIPPED" \ | |
| --arg run_id "${{ github.run_id }}" \ | |
| --arg run_number "${{ github.run_number }}" \ | |
| --arg sha "${{ github.sha }}" \ | |
| '{ | |
| build: { | |
| run_id: ($run_id | tonumber), | |
| run_number: ($run_number | tonumber), | |
| sha: $sha, | |
| timestamp: $ts | |
| }, | |
| latest: $latest, | |
| builds: ( | |
| group_by(.version) | |
| | map({ | |
| version: .[0].version, | |
| major: .[0].major, | |
| is_latest: .[0].is_latest, | |
| platforms: (map(.platforms) | add | unique), | |
| variants: ( | |
| map({(.variant): {tags: .tags, platforms: .platforms, digest: .digest}}) | add | |
| ) | |
| }) | |
| | sort_by(.major) | reverse | |
| ) | |
| }' parts/*/part.json > manifest.json | |
| cat manifest.json | |
| - uses: actions/upload-artifact@v4 | |
| with: | |
| name: manifest | |
| path: manifest.json |