GitHub Actions docker/metadata-action Produces Empty Tags
docker/metadata-action generated no tags or labels, so the subsequent build/push has nothing to tag. Usually the images input is missing or the tag rules do not match the current event.
What this error means
The metadata step succeeds but steps.meta.outputs.tags is empty, and docker/build-push-action either pushes an untagged image or fails because no tags were provided.
# meta produced nothing because no images were given
- id: meta
uses: docker/metadata-action@v5
# tags: ${{ steps.meta.outputs.tags }} -> emptyDiagnose it: was the cache hit, and was it the right one?
Cache bugs split into three shapes and they need different fixes: the cache never saved, it saved but the key never matches on restore, or it restored a stale entry through a restore-keys prefix and is now poisoning the build. The step output tells you which one you have.
- uses: actions/cache@v4
id: cache
with:
path: ~/.npm
key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-npm-
- name: What happened
run: |
echo "exact hit: ${{ steps.cache.outputs.cache-hit }}"
echo "key used: ${{ steps.cache.outputs.cache-matched-key }}"Common causes
Missing images input
metadata-action needs the images input (the registry/repo) to construct tags. Without it, the tag list is empty even if tag rules are defined.
Tag rules do not match the event
The default and custom tag rules are event-sensitive (for example type=ref or type=semver). On an event none of the rules target, no tags are generated.
How to fix it
Provide images and explicit tag rules
- id: meta
uses: docker/metadata-action@v5
with:
images: ghcr.io/${{ github.repository }}
tags: |
type=ref,event=branch
type=semver,pattern={{version}}
- uses: docker/build-push-action@v6
with:
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}Match tag rules to the trigger
- Give the metadata step an id and read steps.<id>.outputs.tags.
- Add a type=sha or type=raw tag so something is always produced.
- Ensure semver rules only fire on tag events, and ref rules on branch/PR events.
Cache limits that produce confusing failures
- Repository cache is capped at 10 GB. Past that, GitHub evicts least-recently-used entries, so a large cache can silently stop persisting.
- Caches are scoped by branch. A cache written on a feature branch is not visible to another feature branch, only to its base and its own descendants.
- An entry not read for 7 days is evicted, so a rarely-run workflow effectively never has a warm cache.
- Restoring a cache built for a different tool version is worse than a cold start, because you get a corrupted tree instead of a clean install. Always include the tool version in the key.
How to prevent it
- Always pass the images input to metadata-action.
- Include a fallback tag rule (type=sha) so tags are never empty.
- Read tags/labels via the step id outputs.