Node + Puppeteer images #6817
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: Node + Puppeteer images | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| release_tag: | |
| description: "Tag for the images (e.g.: beta)" | |
| required: true | |
| apify_version: | |
| description: "Apify SDK version (e.g.: ^1.0.0). If missing, the latest version will be used." | |
| required: false | |
| crawlee_version: | |
| description: "Crawlee version (e.g.: ^1.0.0). If missing, the latest version will be used." | |
| required: false | |
| rebuild_images: | |
| description: "Rebuilds images even if the cache state matches the current state." | |
| required: false | |
| type: boolean | |
| trigger_templates_pr: | |
| description: "When set to true, always triggers the workflow on actor-templates." | |
| required: false | |
| type: boolean | |
| repository_dispatch: | |
| types: | |
| - build-node-images | |
| pull_request: | |
| paths: | |
| - "node-puppeteer/**" | |
| - ".github/workflows/release-node-puppeteer.yaml" | |
| - ".github/actions/version-matrix/**" | |
| - ".github/scripts/prepare-node-image-tags.js" | |
| - "Makefile" | |
| schedule: | |
| - cron: 0 */2 * * * | |
| env: | |
| RELEASE_TAG: ${{ github.event.inputs.release_tag || github.event.client_payload.release_tag }} | |
| APIFY_VERSION: ${{ github.event.inputs.apify_version || github.event.client_payload.apify_version }} | |
| CRAWLEE_VERSION: ${{ github.event.inputs.crawlee_version || github.event.client_payload.crawlee_version }} | |
| SHOULD_USE_LAST_FIVE: ${{ github.event_name != 'pull_request' }} | |
| SKIP_CACHE_CHECK: ${{ github.event_name == 'pull_request' || github.event.inputs.rebuild_images == 'true' }} | |
| jobs: | |
| matrix: | |
| runs-on: ubuntu-latest | |
| outputs: | |
| matrix: ${{ steps.set-matrix.outputs.matrix }} | |
| steps: | |
| - uses: actions/checkout@v6 | |
| with: | |
| token: ${{ secrets.APIFY_SERVICE_ACCOUNT_GITHUB_TOKEN }} | |
| fetch-depth: 0 | |
| - name: Set up Node.js | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version-file: .github/actions/version-matrix/package.json | |
| cache: yarn | |
| cache-dependency-path: .github/actions/version-matrix/yarn.lock | |
| - run: yarn | |
| working-directory: ./.github/actions/version-matrix | |
| - name: Generate matrix | |
| id: set-matrix | |
| run: echo "matrix=$(yarn node:puppeteer)" >> $GITHUB_OUTPUT | |
| working-directory: ./.github/actions/version-matrix | |
| - name: Print matrix | |
| run: | | |
| echo '${{ steps.set-matrix.outputs.matrix }}' | jq -r '.include[] | "node-version=\(.["node-version"]) apify-version=\(.["apify-version"]) crawlee-version=\(.["crawlee-version"]) puppeteer-version=\(.["puppeteer-version"]) is-latest=\(.["is-latest"])"' | |
| echo "" | |
| echo "Raw matrix:" | |
| echo "" | |
| echo '${{ steps.set-matrix.outputs.matrix }}' | jq -e | |
| - name: Commit updated matrix | |
| id: commit | |
| if: github.event_name != 'pull_request' | |
| uses: apify/actions/signed-commit@v1.2.0 | |
| with: | |
| message: "chore(docker): update ${{ env.RELEASE_TAG || 'latest' }} node:puppeteer-chrome cache" | |
| add: ./.github/actions/version-matrix/data/*.json | |
| retries: 5 | |
| pull: '--rebase --autostash' | |
| github-token: ${{ secrets.APIFY_SERVICE_ACCOUNT_GITHUB_TOKEN }} | |
| - name: Trigger workflow on actor-templates | |
| if: (steps.commit.outputs.committed == 'true' || github.event.inputs.trigger_templates_pr == 'true') && steps.set-matrix.outputs.latest-runtime-version != '' | |
| uses: peter-evans/repository-dispatch@v4 | |
| with: | |
| token: ${{ secrets.APIFY_SERVICE_ACCOUNT_GITHUB_TOKEN }} | |
| repository: apify/actor-templates | |
| event-type: update-templates | |
| client-payload: |- | |
| { | |
| "base_image": "apify/actor-node-puppeteer-chrome", | |
| "module_version": "${{ steps.set-matrix.outputs.latest-module-version }}", | |
| "default_runtime_version": "${{ steps.set-matrix.outputs.latest-runtime-version }}" | |
| } | |
| # Build master images that are not dependent on existing builds. | |
| build-main: | |
| needs: [matrix] | |
| runs-on: ubuntu-latest | |
| if: ${{ toJson(fromJson(needs.matrix.outputs.matrix).include) != '[]' }} | |
| strategy: | |
| # By the time some build fails, other build can be already finished | |
| # so fail-fast does not really prevent the publishing of all parallel builds | |
| fail-fast: false | |
| matrix: ${{ fromJson(needs.matrix.outputs.matrix) }} | |
| name: "node: ${{ matrix.node-version }}, apify: ${{ matrix.apify-version }}, crawlee: ${{ matrix.crawlee-version }}, pptr: ${{ matrix.puppeteer-version }}, is-latest: ${{ matrix.is-latest }}" | |
| steps: | |
| - name: Set default inputs if event is pull request | |
| if: github.event_name == 'pull_request' | |
| run: | | |
| if [[ -z "$RELEASE_TAG" ]]; then echo "RELEASE_TAG=CI_TEST" >> $GITHUB_ENV; fi | |
| if [[ -z "$APIFY_VERSION" ]]; then echo "APIFY_VERSION=${{ matrix.apify-version }}" >> $GITHUB_ENV; fi | |
| if [[ -z "$CRAWLEE_VERSION" ]]; then echo "CRAWLEE_VERSION=${{ matrix.crawlee-version }}" >> $GITHUB_ENV; fi | |
| - name: Set default inputs if event is schedule | |
| if: github.event_name == 'schedule' | |
| run: | | |
| if [[ -z "$RELEASE_TAG" ]]; then echo "RELEASE_TAG=latest" >> $GITHUB_ENV; fi | |
| if [[ -z "$APIFY_VERSION" ]]; then echo "APIFY_VERSION=${{ matrix.apify-version }}" >> $GITHUB_ENV; fi | |
| if [[ -z "$CRAWLEE_VERSION" ]]; then echo "CRAWLEE_VERSION=${{ matrix.crawlee-version }}" >> $GITHUB_ENV; fi | |
| - name: Set default inputs if event is workflow_dispatch or repository_dispatch | |
| if: github.event_name == 'workflow_dispatch' || github.event_name == 'repository_dispatch' | |
| run: | | |
| if [[ -z "$APIFY_VERSION" ]]; then echo "APIFY_VERSION=${{ matrix.apify-version }}" >> $GITHUB_ENV; fi | |
| if [[ -z "$CRAWLEE_VERSION" ]]; then echo "CRAWLEE_VERSION=${{ matrix.crawlee-version }}" >> $GITHUB_ENV; fi | |
| - name: Check if inputs are set correctly | |
| run: | | |
| if [[ -z "$RELEASE_TAG" ]]; then echo "RELEASE_TAG input is empty!" >&2; exit 1; fi | |
| if [[ -z "$APIFY_VERSION" ]]; then echo "APIFY_VERSION input is empty!" >&2; exit 1; fi | |
| if [[ -z "$CRAWLEE_VERSION" ]]; then echo "CRAWLEE_VERSION input is empty!" >&2; exit 1; fi | |
| - name: Checkout | |
| uses: actions/checkout@v6 | |
| - name: Set up Node ${{ matrix.node-version }} | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version: ${{ matrix.node-version }} | |
| - name: Set Dependency Versions | |
| run: | | |
| cd ${{ matrix.image-name }} | |
| node ../.github/scripts/set-dependency-versions.js | |
| env: | |
| PUPPETEER_VERSION: ${{ matrix.puppeteer-version }} | |
| - # It seems that it takes at least two minutes before a newly published version | |
| # becomes available in the NPM registry. We wait before starting the image builds. | |
| name: Wait For Package Registry | |
| uses: nick-fields/retry@v3 | |
| with: | |
| timeout_minutes: 2 # timeout for a single attempt | |
| max_attempts: 5 | |
| retry_wait_seconds: 60 # wait between retries | |
| command: cd ${{ matrix.image-name }} && npm i --dry-run | |
| - name: Prepare image tags | |
| id: prepare-tags | |
| uses: actions/github-script@v8 | |
| env: | |
| CURRENT_NODE: ${{ matrix.node-version }} | |
| LATEST_NODE: ${{ matrix.latest-node-version }} | |
| FRAMEWORK_VERSION: ${{ matrix.puppeteer-version }} | |
| RELEASE_TAG: ${{ env.RELEASE_TAG }} | |
| IMAGE_NAME: apify/actor-${{ matrix.image-name }} | |
| IS_LATEST_BROWSER_IMAGE: ${{ matrix.is-latest }} | |
| with: | |
| script: | | |
| const generateTags = require("./.github/scripts/prepare-node-image-tags.js"); | |
| return generateTags(); | |
| - name: Prepare slim image tags | |
| id: prepare-slim-tags | |
| uses: actions/github-script@v8 | |
| env: | |
| CURRENT_NODE: ${{ matrix.node-version }} | |
| LATEST_NODE: ${{ matrix.latest-node-version }} | |
| FRAMEWORK_VERSION: ${{ matrix.puppeteer-version }} | |
| RELEASE_TAG: ${{ env.RELEASE_TAG }} | |
| IMAGE_NAME: apify/actor-${{ matrix.image-name }} | |
| IS_LATEST_BROWSER_IMAGE: ${{ matrix.is-latest }} | |
| TAG_SUFFIX: "-slim" | |
| with: | |
| script: | | |
| const generateTags = require("./.github/scripts/prepare-node-image-tags.js"); | |
| return generateTags(); | |
| - name: Set up QEMU | |
| uses: docker/setup-qemu-action@v3 | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@v4 | |
| - name: Build and tag image for testing | |
| uses: docker/build-push-action@v6 | |
| with: | |
| context: ./${{ matrix.image-name }} | |
| file: ./${{ matrix.image-name }}/Dockerfile | |
| build-args: | | |
| NODE_VERSION=${{ matrix.node-version }} | |
| PUPPETEER_VERSION=${{ matrix.puppeteer-version }} | |
| platforms: linux/amd64 | |
| provenance: false | |
| load: true | |
| tags: ${{ fromJson(steps.prepare-tags.outputs.result).allTags }} | |
| cache-from: type=gha,scope=${{ matrix.image-name }}-${{ matrix.node-version }}-${{ matrix.puppeteer-version }} | |
| cache-to: type=gha,mode=max,scope=${{ matrix.image-name }}-${{ matrix.node-version }}-${{ matrix.puppeteer-version }} | |
| - name: Test image | |
| run: docker run ${{ fromJson(steps.prepare-tags.outputs.result).firstImageName }} | |
| - name: Test package manager configuration | |
| run: | | |
| docker run --rm ${{ fromJson(steps.prepare-tags.outputs.result).firstImageName }} sh -c ' | |
| set -ex | |
| test "$(npm config get cache)" = "$HOME/.npm" | |
| test ! -L "$HOME/.npm" | |
| test ! -e /pkg-cache/npm | |
| npm cache add is-number@7.0.0 | |
| test -d "$HOME/.npm/_cacache" | |
| rm -rf "$HOME/.npm" | |
| test ! -e "$HOME/.npm" | |
| test -z "$(ls -A "$COREPACK_HOME")" | |
| pnpm store path | grep -q "^/pkg-cache/pnpm/store" | |
| test "$(pnpm config get node-linker)" = "hoisted" | |
| corepack pnpm@10 store path | grep -q "^/pkg-cache/pnpm/store" | |
| if npm config list 2>&1 | grep -qi "unknown"; then exit 1; fi | |
| yarn cache dir | grep -q "^/pkg-cache/yarn" | |
| ' | |
| - name: Build and tag slim image for testing | |
| uses: docker/build-push-action@v6 | |
| with: | |
| context: ./${{ matrix.image-name }} | |
| file: ./${{ matrix.image-name }}/Dockerfile | |
| build-args: | | |
| NODE_VERSION=${{ matrix.node-version }} | |
| PUPPETEER_VERSION=${{ matrix.puppeteer-version }} | |
| SLIM=1 | |
| platforms: linux/amd64 | |
| provenance: false | |
| load: true | |
| tags: ${{ fromJson(steps.prepare-slim-tags.outputs.result).allTags }} | |
| cache-from: | | |
| type=gha,scope=${{ matrix.image-name }}-${{ matrix.node-version }}-${{ matrix.puppeteer-version }}-slim | |
| type=gha,scope=${{ matrix.image-name }}-${{ matrix.node-version }}-${{ matrix.puppeteer-version }} | |
| cache-to: type=gha,mode=max,scope=${{ matrix.image-name }}-${{ matrix.node-version }}-${{ matrix.puppeteer-version }}-slim | |
| - name: Test slim image | |
| run: docker run ${{ fromJson(steps.prepare-slim-tags.outputs.result).firstImageName }} | |
| - name: Measure image size | |
| id: image-size | |
| if: github.event_name == 'pull_request' | |
| env: | |
| MATRIX_JSON: ${{ toJSON(matrix) }} | |
| NEW_IMAGE: ${{ fromJson(steps.prepare-tags.outputs.result).firstImageName }} | |
| BASE_IMAGE: apify/actor-${{ matrix.image-name }}:${{ matrix.node-version }} | |
| NEW_SLIM_IMAGE: ${{ fromJson(steps.prepare-slim-tags.outputs.result).firstImageName }} | |
| BASE_SLIM_IMAGE: apify/actor-${{ matrix.image-name }}:${{ matrix.node-version }}-slim | |
| run: | | |
| set -euo pipefail | |
| mkdir -p size-report | |
| id="$(printf '%s' "$MATRIX_JSON" | sha256sum | cut -c1-16)" | |
| measure() { | |
| local new_image="$1" base_image="$2" variant="$3" out="$4" | |
| local new_bytes current_bytes | |
| new_bytes="$(docker image inspect "$new_image" --format '{{.Size}}')" | |
| if docker pull --platform linux/amd64 "$base_image" >/dev/null 2>&1; then | |
| current_bytes="$(docker image inspect "$base_image" --format '{{.Size}}')" | |
| else | |
| current_bytes="" | |
| echo "No published baseline image found for ${base_image}" | |
| fi | |
| jq -n \ | |
| --argjson matrix "$MATRIX_JSON" \ | |
| --arg base "$base_image" \ | |
| --arg variant "$variant" \ | |
| --arg current "$current_bytes" \ | |
| --arg new "$new_bytes" \ | |
| '{matrix: $matrix, baseImage: $base, variant: $variant, currentBytes: $current, newBytes: $new}' \ | |
| > "size-report/${out}.json" | |
| } | |
| measure "$NEW_IMAGE" "$BASE_IMAGE" "" "$id" | |
| measure "$NEW_SLIM_IMAGE" "$BASE_SLIM_IMAGE" "slim" "${id}-slim" | |
| echo "id=${id}" >> "$GITHUB_OUTPUT" | |
| - name: Upload image size report | |
| if: github.event_name == 'pull_request' | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: image-size-${{ steps.image-size.outputs.id }} | |
| path: size-report/ | |
| retention-days: 1 | |
| - name: Login to DockerHub | |
| if: github.event_name != 'pull_request' | |
| uses: docker/login-action@v4 | |
| with: | |
| username: ${{ secrets.APIFY_SERVICE_ACCOUNT_DOCKERHUB_USERNAME }} | |
| password: ${{ secrets.APIFY_SERVICE_ACCOUNT_DOCKERHUB_TOKEN }} | |
| - name: Build and push OCI image | |
| if: github.event_name != 'pull_request' | |
| uses: docker/build-push-action@v6 | |
| with: | |
| context: ./${{ matrix.image-name }} | |
| file: ./${{ matrix.image-name }}/Dockerfile | |
| build-args: | | |
| NODE_VERSION=${{ matrix.node-version }} | |
| PUPPETEER_VERSION=${{ matrix.puppeteer-version }} | |
| platforms: linux/amd64 | |
| provenance: true | |
| push: true | |
| tags: ${{ fromJson(steps.prepare-tags.outputs.result).allTags }} | |
| outputs: type=image,oci-mediatypes=true | |
| cache-from: type=gha,scope=${{ matrix.image-name }}-${{ matrix.node-version }}-${{ matrix.puppeteer-version }} | |
| - name: Build and push slim OCI image | |
| if: github.event_name != 'pull_request' | |
| uses: docker/build-push-action@v6 | |
| with: | |
| context: ./${{ matrix.image-name }} | |
| file: ./${{ matrix.image-name }}/Dockerfile | |
| build-args: | | |
| NODE_VERSION=${{ matrix.node-version }} | |
| PUPPETEER_VERSION=${{ matrix.puppeteer-version }} | |
| SLIM=1 | |
| platforms: linux/amd64 | |
| provenance: true | |
| push: true | |
| tags: ${{ fromJson(steps.prepare-slim-tags.outputs.result).allTags }} | |
| outputs: type=image,oci-mediatypes=true | |
| cache-from: | | |
| type=gha,scope=${{ matrix.image-name }}-${{ matrix.node-version }}-${{ matrix.puppeteer-version }}-slim | |
| type=gha,scope=${{ matrix.image-name }}-${{ matrix.node-version }}-${{ matrix.puppeteer-version }} | |
| # Aggregate the per-image size reports uploaded by the build matrix and post/update | |
| # a single sticky PR comment comparing current vs new image sizes. | |
| size-report: | |
| name: Report image size changes | |
| needs: [build-main] | |
| if: ${{ always() && github.event_name == 'pull_request' }} | |
| runs-on: ubuntu-latest | |
| permissions: | |
| pull-requests: write | |
| # All image workflows update sections of ONE shared PR comment; the repo-wide | |
| # concurrency group serializes their read-modify-write so no section is lost. | |
| concurrency: | |
| group: image-size-comment-${{ github.event.pull_request.number }} | |
| cancel-in-progress: false | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v6 | |
| - name: Download image size reports | |
| uses: actions/download-artifact@v8 | |
| continue-on-error: true | |
| with: | |
| pattern: image-size-* | |
| path: size-reports | |
| merge-multiple: true | |
| - name: Set up Node.js | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version: 24 | |
| - name: Render comment section | |
| run: | | |
| mkdir -p size-reports | |
| node .github/scripts/format-image-size-report.ts size-reports "Node + Puppeteer images" "${{ github.event.pull_request.head.sha }}" | tee image-size-comment.md | |
| - name: Post or update image size comment | |
| uses: actions/github-script@v8 | |
| env: | |
| SECTION_KEY: node-puppeteer | |
| with: | |
| script: | | |
| const { readFileSync } = require('node:fs'); | |
| const upsertSection = require('./.github/scripts/upsert-image-size-comment.js'); | |
| await upsertSection({ github, context }, process.env.SECTION_KEY, readFileSync('image-size-comment.md', 'utf8')); |