3939
4040 # Login to DockerHub first, to avoid rate-limiting
4141 - uses : docker/login-action@v3
42+ # PRs from forks don't have access to secrets, disable this step in that case.
43+ if : ${{ github.event.pull_request.head.repo.full_name == 'astral-sh/uv' }}
4244 with :
4345 username : astralshbot
4446 password : ${{ secrets.DOCKERHUB_TOKEN_RO }}
@@ -164,6 +166,10 @@ jobs:
164166 needs :
165167 - docker-publish
166168 if : ${{ inputs.plan != '' && !fromJson(inputs.plan).announcement_tag_is_implicit }}
169+ permissions :
170+ packages : write
171+ attestations : write # needed to push image attestations to the Github attestation store
172+ id-token : write # needed for signing the images with GitHub OIDC Token
167173 strategy :
168174 fail-fast : false
169175 matrix :
@@ -198,6 +204,8 @@ jobs:
198204 username : astralshbot
199205 password : ${{ secrets.DOCKERHUB_TOKEN_RO }}
200206
207+ -
uses :
sigstore/[email protected] 208+
201209 - uses : docker/setup-buildx-action@v3
202210
203211 - uses : docker/login-action@v3
@@ -260,6 +268,7 @@ jobs:
260268 ${{ env.TAG_PATTERNS }}
261269
262270 - name : Build and push
271+ id : build-and-push
263272 uses : docker/build-push-action@v6
264273 with :
265274 context : .
@@ -272,6 +281,13 @@ jobs:
272281 labels : ${{ steps.meta.outputs.labels }}
273282 annotations : ${{ steps.meta.outputs.annotations }}
274283
284+ - name : Generate artifact attestation
285+ uses : actions/attest-build-provenance@v2
286+ with :
287+ subject-name : ${{ env.UV_BASE_IMG}}
288+ subject-digest : ${{ steps.build-and-push.outputs.digest }}
289+ # push-to-registry is explicitly not enabled to maintain full control over the top image
290+
275291 # This is effectively a duplicate of `docker-publish` to make https://github.com/astral-sh/uv/pkgs/container/uv
276292 # show the uv base image first since GitHub always shows the last updated image digests
277293 # This works by annotating the original digests (previously non-annotated) which triggers an update to ghcr.io
@@ -283,6 +299,10 @@ jobs:
283299 needs :
284300 - docker-publish-extra
285301 if : ${{ inputs.plan != '' && !fromJson(inputs.plan).announcement_tag_is_implicit }}
302+ permissions :
303+ packages : write
304+ attestations : write # needed to push image attestations to the Github attestation store
305+ id-token : write # needed for signing the images with GitHub OIDC Token
286306 steps :
287307 # Login to DockerHub first, to avoid rate-limiting
288308 - uses : docker/login-action@v3
@@ -330,3 +350,29 @@ jobs:
330350 "${annotations[@]}" \
331351 $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
332352 $(printf '${{ env.UV_BASE_IMG }}@sha256:%s ' *)
353+
354+ - name : Share manifest digest
355+ id : manifest-digest
356+ # To sign the manifest, we need it's digest. Unfortunately "docker
357+ # buildx imagetools create" does not (yet) have a clean way of sharing
358+ # the digest of the manifest it creates (see docker/buildx#2407), so
359+ # we use a separate command to retrieve it.
360+ # imagetools inspect [TAG] --format '{{json .Manifest}}' gives us
361+ # the machine readable JSON description of the manifest, and the
362+ # jq command extracts the digest from this. The digest is then
363+ # sent to the Github step output file for sharing with other steps.
364+ run : |
365+ digest="$(
366+ docker buildx imagetools inspect \
367+ "${UV_BASE_IMG}:${DOCKER_METADATA_OUTPUT_VERSION}" \
368+ --format '{{json .Manifest}}' \
369+ | jq -r '.digest'
370+ )"
371+ echo "digest=${digest}" >> "$GITHUB_OUTPUT"
372+
373+ - name : Generate artifact attestation
374+ uses : actions/attest-build-provenance@v2
375+ with :
376+ subject-name : ${{ env.UV_BASE_IMG}}
377+ subject-digest : ${{ steps.manifest-digest.outputs.digest }}
378+ # push-to-registry is explicitly not enabled to maintain full control over the top image
0 commit comments