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 :
@@ -260,6 +266,7 @@ jobs:
260266 ${{ env.TAG_PATTERNS }}
261267
262268 - name : Build and push
269+ id : build-and-push
263270 uses : docker/build-push-action@v6
264271 with :
265272 context : .
@@ -272,6 +279,13 @@ jobs:
272279 labels : ${{ steps.meta.outputs.labels }}
273280 annotations : ${{ steps.meta.outputs.annotations }}
274281
282+ - name : Generate artifact attestation
283+ uses : actions/attest-build-provenance@v2
284+ with :
285+ subject-name : ${{ env.UV_BASE_IMG }}
286+ subject-digest : ${{ steps.build-and-push.outputs.digest }}
287+ # push-to-registry is explicitly not enabled to maintain full control over the top image
288+
275289 # This is effectively a duplicate of `docker-publish` to make https://github.com/astral-sh/uv/pkgs/container/uv
276290 # show the uv base image first since GitHub always shows the last updated image digests
277291 # This works by annotating the original digests (previously non-annotated) which triggers an update to ghcr.io
@@ -283,6 +297,10 @@ jobs:
283297 needs :
284298 - docker-publish-extra
285299 if : ${{ inputs.plan != '' && !fromJson(inputs.plan).announcement_tag_is_implicit }}
300+ permissions :
301+ packages : write
302+ attestations : write # needed to push image attestations to the Github attestation store
303+ id-token : write # needed for signing the images with GitHub OIDC Token
286304 steps :
287305 # Login to DockerHub first, to avoid rate-limiting
288306 - uses : docker/login-action@v3
@@ -330,3 +348,29 @@ jobs:
330348 "${annotations[@]}" \
331349 $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
332350 $(printf '${{ env.UV_BASE_IMG }}@sha256:%s ' *)
351+
352+ - name : Share manifest digest
353+ id : manifest-digest
354+ # To sign the manifest, we need it's digest. Unfortunately "docker
355+ # buildx imagetools create" does not (yet) have a clean way of sharing
356+ # the digest of the manifest it creates (see docker/buildx#2407), so
357+ # we use a separate command to retrieve it.
358+ # imagetools inspect [TAG] --format '{{json .Manifest}}' gives us
359+ # the machine readable JSON description of the manifest, and the
360+ # jq command extracts the digest from this. The digest is then
361+ # sent to the Github step output file for sharing with other steps.
362+ run : |
363+ digest="$(
364+ docker buildx imagetools inspect \
365+ "${UV_BASE_IMG}:${DOCKER_METADATA_OUTPUT_VERSION}" \
366+ --format '{{json .Manifest}}' \
367+ | jq -r '.digest'
368+ )"
369+ echo "digest=${digest}" >> "$GITHUB_OUTPUT"
370+
371+ - name : Generate artifact attestation
372+ uses : actions/attest-build-provenance@v2
373+ with :
374+ subject-name : ${{ env.UV_BASE_IMG }}
375+ subject-digest : ${{ steps.manifest-digest.outputs.digest }}
376+ # push-to-registry is explicitly not enabled to maintain full control over the top image
0 commit comments