@@ -285,8 +285,49 @@ jobs:
285285 # - Full registry path (ghcr.io/owner/repo) for production images
286286 # - Multiple tags for flexibility (full version, minor version, latest)
287287 # - Comprehensive OCI labels for traceability and compliance
288- - name : Extract metadata
289- id : meta
288+ #
289+ # Note: We extract metadata twice - once for validation (manifest-only annotations
290+ # since index annotations aren't supported when loading to Docker daemon) and once
291+ # for push (manifest+index annotations for full registry support).
292+ - name : Extract metadata for validation
293+ id : meta_validate
294+ uses : docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5
295+ with :
296+ # Full registry path for production images (ghcr.io/owner/repo)
297+ images : ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
298+ tags : |
299+ type=semver,pattern={{version}}
300+ type=semver,pattern={{major}}.{{minor}}
301+ type=raw,value=latest
302+ labels : |
303+ org.opencontainers.image.title=${{ env.DOCKER_IMAGE_TITLE }}
304+ org.opencontainers.image.description=${{ env.DOCKER_IMAGE_DESCRIPTION }}
305+ org.opencontainers.image.source=${{ env.DOCKER_IMAGE_SOURCE }}
306+ org.opencontainers.image.licenses=${{ env.DOCKER_IMAGE_LICENSE }}
307+ org.opencontainers.image.authors=${{ env.DOCKER_IMAGE_AUTHORS }}
308+ org.opencontainers.image.vendor=${{ env.DOCKER_IMAGE_VENDOR }}
309+ org.opencontainers.image.revision=${{ github.sha }}
310+ org.opencontainers.image.documentation=${{ env.DOCKER_IMAGE_DOCS }}
311+ # GHCR often reads description from OCI annotations (manifest/index)
312+ # rather than only image config labels, especially when Buildx produces
313+ # an OCI index (e.g. when provenance/attestations are attached).
314+ # For validation builds (load: true), we only use manifest annotations
315+ # since index annotations aren't supported for single-platform Docker exports.
316+ annotations : |
317+ org.opencontainers.image.title=${{ env.DOCKER_IMAGE_TITLE }}
318+ org.opencontainers.image.description=${{ env.DOCKER_IMAGE_DESCRIPTION }}
319+ org.opencontainers.image.source=${{ env.DOCKER_IMAGE_SOURCE }}
320+ org.opencontainers.image.licenses=${{ env.DOCKER_IMAGE_LICENSE }}
321+ org.opencontainers.image.authors=${{ env.DOCKER_IMAGE_AUTHORS }}
322+ org.opencontainers.image.vendor=${{ env.DOCKER_IMAGE_VENDOR }}
323+ org.opencontainers.image.revision=${{ github.sha }}
324+ org.opencontainers.image.documentation=${{ env.DOCKER_IMAGE_DOCS }}
325+ env :
326+ # Only manifest annotations for validation builds (load: true)
327+ # Index annotations aren't supported when loading to Docker daemon
328+ DOCKER_METADATA_ANNOTATIONS_LEVELS : manifest
329+ - name : Extract metadata for push
330+ id : meta_push
290331 uses : docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5
291332 with :
292333 # Full registry path for production images (ghcr.io/owner/repo)
@@ -318,6 +359,7 @@ jobs:
318359 org.opencontainers.image.documentation=${{ env.DOCKER_IMAGE_DOCS }}
319360 env :
320361 # Place annotations on both the manifest and (when present) the index.
362+ # Index annotations are supported when pushing to registry.
321363 DOCKER_METADATA_ANNOTATIONS_LEVELS : manifest,index
322364 - name : Generate Release Version
323365 id : release_version
@@ -370,11 +412,12 @@ jobs:
370412 type=gha,mode=max
371413 # Image tags for identification (semver versions + latest from metadata extraction)
372414 # Multiple tags created: v1.2.3, 1.2, 1, latest (for default branch)
373- tags : ${{ steps.meta .outputs.tags }}
415+ tags : ${{ steps.meta_validate .outputs.tags }}
374416 # OCI labels for image metadata (title, description, source, license, etc.)
375- labels : ${{ steps.meta .outputs.labels }}
417+ labels : ${{ steps.meta_validate .outputs.labels }}
376418 # OCI annotations for registry UIs (e.g. GHCR package description)
377- annotations : ${{ steps.meta.outputs.annotations }}
419+ # Using manifest-only annotations since index annotations aren't supported when loading
420+ annotations : ${{ steps.meta_validate.outputs.annotations }}
378421 # Disable attestations for validation build (faster validation, attestations added in push step)
379422 # Note: Attestations (SBOM/provenance) are only generated when pushing, not when loading to daemon
380423 provenance : false
@@ -393,7 +436,7 @@ jobs:
393436 uses : docker/scout-action@f8c776824083494ab0d56b8105ba2ca85c86e4de # v1
394437 with :
395438 command : cves
396- image : ${{ steps.meta .outputs.tags }}
439+ image : ${{ steps.meta_validate .outputs.tags }}
397440 only-severities : critical,high
398441 exit-code : 1
399442 github-token : ${{ secrets.GITHUB_TOKEN }}
@@ -430,11 +473,12 @@ jobs:
430473 type=gha,mode=max
431474 # Image tags for identification (semver versions + latest from metadata extraction)
432475 # Multiple tags created: v1.2.3, 1.2, 1, latest (for default branch)
433- tags : ${{ steps.meta .outputs.tags }}
476+ tags : ${{ steps.meta_push .outputs.tags }}
434477 # OCI labels for image metadata (title, description, source, license, etc.)
435- labels : ${{ steps.meta .outputs.labels }}
478+ labels : ${{ steps.meta_push .outputs.labels }}
436479 # OCI annotations for registry UIs (e.g. GHCR package description)
437- annotations : ${{ steps.meta.outputs.annotations }}
480+ # Using manifest+index annotations for full registry support
481+ annotations : ${{ steps.meta_push.outputs.annotations }}
438482 # Enable SBOM and provenance for supply chain security
439483 # SBOM: Software Bill of Materials for dependency tracking
440484 # Provenance: Build attestations for supply chain security (mode=max includes all metadata)
0 commit comments