Skip to content

Update heroku/buildpacks-python to v6.6.0 (#1031) #2768

Update heroku/buildpacks-python to v6.6.0 (#1031)

Update heroku/buildpacks-python to v6.6.0 (#1031) #2768

name: Build, Test, Publish
on:
pull_request:
push:
branches: ["main"]
schedule:
- cron: "0 0 * * 1-5"
workflow_dispatch:
permissions:
contents: read
id-token: write
defaults:
run:
# Setting an explicit bash shell ensures GitHub Actions enables pipefail mode too, rather
# than only error on exit. This is important for UX since this workflow uses pipes. See:
# https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsshell
shell: bash
jobs:
create:
strategy:
fail-fast: false
matrix:
builder: ["builder-22", "builder-24", "builder-26"]
arch: ["amd64", "arm64"]
exclude:
# Builders prior to Heroku-24 don't support ARM. We're using an exclude rather than
# an include so that the ARM jobs don't appear out of sequence in the GitHub UI.
- builder: builder-22
arch: arm64
runs-on: ${{ matrix.arch == 'arm64' && 'ubuntu-24.04-arm' || 'ubuntu-24.04' }}
steps:
- name: Checkout
uses: actions/checkout@v7
- name: Install Pack CLI
uses: buildpacks/github-actions/setup-pack@36416a96be10a6cc7170e7c9c63656d4092f78b2 # v6.1.0
- name: Create builder image
run: pack builder create ${{ matrix.builder }} --config ${{ matrix.builder }}/builder.toml --pull-policy always
# We export the run image too (and not just the generated builder image), since it adds virtually
# no size to the archive (since the layers are mostly duplicates), and it ends up being quicker
# than docker pulling the run image in the jobs that perform the pack build.
# We manually compress the archive rather than relying upon actions/cache's compression, since
# it ends up being faster both in this job and also later when the images are consumed.
- name: Export Docker images from the Docker daemon
# Using sed rather than yq until this yq bug is fixed:
# https://github.com/mikefarah/yq/issues/1758
run: |
RUN_IMAGE_TAG=$(sed --quiet --regexp-extended --expression 's/run-image\s*=\s*"(.+)"/\1/p' ${{ matrix.builder }}/builder.toml)
docker save ${{ matrix.builder }} ${RUN_IMAGE_TAG} | zstd -T0 --long=31 -o images.tar.zst
# We use a cache here rather than artifacts because it's 4x faster and we
# don't need the builder archive outside of this workflow run anyway.
- name: Save Docker images to the cache
uses: actions/cache/save@v6
with:
key: ${{ github.run_id}}-${{ matrix.builder }}-${{ matrix.arch }}
path: images.tar.zst
test:
runs-on: ${{ matrix.arch == 'arm64' && 'ubuntu-24.04-arm' || 'ubuntu-24.04' }}
needs: create
strategy:
fail-fast: false
matrix:
language:
[
"dotnet",
"go",
"gradle",
"java",
"nodejs",
"php",
"python",
"ruby",
"scala",
]
builder: ["builder-22", "builder-24", "builder-26"]
arch: ["amd64", "arm64"]
exclude:
- builder: builder-22
arch: arm64
steps:
- name: Checkout getting started guide
uses: actions/checkout@v7
with:
ref: main
repository: heroku/${{ matrix.language }}-getting-started.git
- name: Install Pack CLI
uses: buildpacks/github-actions/setup-pack@36416a96be10a6cc7170e7c9c63656d4092f78b2 # v6.1.0
- name: Restore Docker images from the cache
uses: actions/cache/restore@v6
with:
fail-on-cache-miss: true
key: ${{ github.run_id}}-${{ matrix.builder }}-${{ matrix.arch }}
path: images.tar.zst
env:
SEGMENT_DOWNLOAD_TIMEOUT_MINS: 1
- name: Load Docker images into the Docker daemon
run: zstd -dc --long=31 images.tar.zst | docker load
- name: Build getting started guide image
run: pack build getting-started --force-color --builder ${{ matrix.builder }} --trust-builder --pull-policy never
- name: Start getting started guide image
run: docker run --name getting-started --detach -p 8080:8080 --env PORT=8080 getting-started
- name: Test getting started web server response
run: |
if curl -sSfL --retry 10 --retry-delay 1 --retry-all-errors --connect-timeout 3 http://localhost:8080 -o response.txt; then
echo "Successful response from server"
else
echo "Server did not respond successfully"
docker logs getting-started
[[ -f response.txt ]] && cat response.txt
exit 1
fi
publish-image:
runs-on: ${{ matrix.arch == 'arm64' && 'ubuntu-24.04-arm' || 'ubuntu-24.04' }}
if: success() && github.ref == 'refs/heads/main'
needs: test
strategy:
fail-fast: false
matrix:
include:
- builder: builder-22
arch: amd64
tag_docker_hub: heroku/builder:22
tag_ecr_public: heroku/builder:22
- builder: builder-24
arch: amd64
tag_docker_hub: heroku/builder:24_linux-amd64
tag_ecr_public: heroku/builder:24_linux-amd64
- builder: builder-24
arch: arm64
tag_docker_hub: heroku/builder:24_linux-arm64
tag_ecr_public: heroku/builder:24_linux-arm64
- builder: builder-26
arch: amd64
tag_docker_hub: heroku/builder:26_linux-amd64
tag_ecr_public: heroku/builder:26_linux-amd64
- builder: builder-26
arch: arm64
tag_docker_hub: heroku/builder:26_linux-arm64
tag_ecr_public: heroku/builder:26_linux-arm64
steps:
- name: Restore Docker images from the cache
uses: actions/cache/restore@v6
with:
fail-on-cache-miss: true
key: ${{ github.run_id}}-${{ matrix.builder }}-${{ matrix.arch }}
path: images.tar.zst
env:
SEGMENT_DOWNLOAD_TIMEOUT_MINS: 1
- name: Load Docker images into the Docker daemon
run: zstd -dc --long=31 images.tar.zst | docker load
- name: Log into Docker Hub
if: matrix.tag_docker_hub != ''
run: echo '${{ secrets.DOCKER_HUB_TOKEN }}' | docker login -u '${{ secrets.DOCKER_HUB_USER }}' --password-stdin
- name: Configure AWS credentials
if: matrix.tag_ecr_public != ''
uses: aws-actions/configure-aws-credentials@e6de054238d6b7531b4efff3b6587d9aade6a06c # v6.2.3
with:
role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/${{ secrets.AWS_ECR_ROLE }}
aws-region: ${{ vars.AWS_REGION }}
- name: Log in to Amazon ECR Public
if: matrix.tag_ecr_public != ''
id: login-ecr-public
uses: aws-actions/amazon-ecr-login@d539f0932e70871a027e9d5a9d8fc38589180a64 # v2.1.6
with:
registry-type: public
- name: Tag builder and push to Docker Hub
if: matrix.tag_docker_hub != ''
run: |
DOCKER_HUB_IMAGE_URI='${{ matrix.tag_docker_hub }}'
set -x
docker tag '${{ matrix.builder }}' "${DOCKER_HUB_IMAGE_URI}"
docker push "${DOCKER_HUB_IMAGE_URI}"
- name: Tag builder and push to public ECR
if: matrix.tag_ecr_public != ''
run: |
ECR_PUBLIC_IMAGE_URI='public.ecr.aws/${{ matrix.tag_ecr_public }}'
set -x
docker tag '${{ matrix.builder }}' "${ECR_PUBLIC_IMAGE_URI}"
docker push "${ECR_PUBLIC_IMAGE_URI}"
publish-manifest:
runs-on: ubuntu-24.04
needs: publish-image
strategy:
fail-fast: false
matrix:
include:
- tag_uri: "docker.io/heroku/builder:24"
manifest_uris: "docker.io/heroku/builder:24_linux-amd64 docker.io/heroku/builder:24_linux-arm64"
- tag_uri: "public.ecr.aws/heroku/builder:24"
manifest_uris: "public.ecr.aws/heroku/builder:24_linux-amd64 public.ecr.aws/heroku/builder:24_linux-arm64"
- tag_uri: "docker.io/heroku/builder:26"
manifest_uris: "docker.io/heroku/builder:26_linux-amd64 docker.io/heroku/builder:26_linux-arm64"
- tag_uri: "public.ecr.aws/heroku/builder:26"
manifest_uris: "public.ecr.aws/heroku/builder:26_linux-amd64 public.ecr.aws/heroku/builder:26_linux-arm64"
steps:
- name: Log in to Docker Hub
run: echo '${{ secrets.DOCKER_HUB_TOKEN }}' | docker login -u '${{ secrets.DOCKER_HUB_USER }}' --password-stdin
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@e6de054238d6b7531b4efff3b6587d9aade6a06c # v6.2.3
with:
role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/${{ secrets.AWS_ECR_ROLE }}
aws-region: ${{ vars.AWS_REGION }}
- name: Log in to Amazon ECR Public
id: login-ecr-public
uses: aws-actions/amazon-ecr-login@d539f0932e70871a027e9d5a9d8fc38589180a64 # v2.1.6
with:
registry-type: public
- name: Create and push manifest lists
run: |
set -x
docker manifest create "${{ matrix.tag_uri }}" ${{ matrix.manifest_uris }}
docker manifest push "${{ matrix.tag_uri }}"
post-honeycomb-marker:
name: Post Honeycomb release marker
needs: publish-manifest
# Every push to main rebuilds and republishes the builder, so each one is a genuine
# release worth tracking. Excludes the nightly `schedule` rebuild and `workflow_dispatch`,
# both of which republish unchanged images.
if: success() && github.event_name == 'push' && github.ref == 'refs/heads/main'
runs-on: ubuntu-24.04
steps:
- name: Checkout
uses: actions/checkout@v7
# Only the tip commit's subject and timestamp are needed to label the release.
with:
fetch-depth: 1
- name: Post Honeycomb release marker
env:
HONEYCOMB_API_KEY: ${{ secrets.HONEYCOMB_API_KEY }}
HONEYCOMB_DATASET: heroku-builds-production
COMMIT_URL: ${{ github.server_url }}/${{ github.repository }}/commit/${{ github.sha }}
run: |
msg=$(git log -1 --format=%s)
# Anchor the marker to the release commit time, not "now": this job may run delayed
# after a re-trigger, and the commit timestamp is stable across re-runs.
start_time=$(git log -1 --format=%ct)
api="https://api.honeycomb.io/1/markers/${HONEYCOMB_DATASET}"
curl=(curl -sS --fail --connect-timeout 10 --retry 3 --retry-all-errors --max-time 30
-H "X-Honeycomb-Team: ${HONEYCOMB_API_KEY}" -H "Content-Type: application/json")
# One marker per builder release; dedup on the commit URL (stable identity for this
# release). If a marker already exists (e.g. a re-run of the workflow), skip — the
# marker is unchanged, so there's nothing to update. This job is the last in the
# workflow and runs after the release has already published, so letting it fail red
# on a Honeycomb error surfaces the problem (retry/bug) without blocking the release.
existing=$("${curl[@]}" "${api}" \
| jq -r --arg url "$COMMIT_URL" 'map(select(.url == $url)) | first | .id // empty')
if [[ -n "$existing" ]]; then
echo "Release marker already exists (${existing}) — skipping."
exit 0
fi
body=$(jq -n --arg msg "$msg" --arg url "$COMMIT_URL" --argjson start "$start_time" \
'{message: $msg, type: "deploy", url: $url, start_time: $start}')
"${curl[@]}" -X POST "${api}" -d "${body}"
echo "Created release marker for ${msg}"