Skip to content

Strip the prefix v from tags created on release event #422

Open

Description

@Nosfistis
          Following-up on this, I am finding that on the release published event the `v` from the tag version is not being stripped from the `{{version}}` variable. This seems incorrect, if the action is supposed to work correctly also on that event type.

          Is this something that was done by design, or instead something that could get updated to match (partially, I agree with the caveats regarding pre-releases) the behaviour on the tag push event?

Originally posted by @esune in #240 (comment)

I realize that this still occurs, and the generated tags contain the v prefix.

Activity

  1. polarathene commented on Feb 25, 2025

    @polarathene

    TL;DR: Everything is working correctly as documented. You have misconfigured.

    Worth noting from the issue you referenced (and the one it references), there is acknowledgement that there is no release event support on this actions docs. It's only "supported" in the sense that the event itself results in the refs/tags/<your tag> event metadata, which if you have a compatible tag rule to match on that it'll be used.


    The v stripping only occurs during the type=semver,pattern= tag rule:

    metadata-action/src/meta.ts

    Lines 172 to 184 in 8e1d546

    const sver = semver.parse(vraw, {
    loose: true
    });
    if (semver.prerelease(vraw)) {
    if (Meta.isRawStatement(tag.attrs['pattern'])) {
    vraw = this.setValue(handlebars.compile(tag.attrs['pattern'])(sver), tag);
    } else {
    vraw = this.setValue(handlebars.compile('{{version}}')(sver), tag);
    }
    } else {
    vraw = this.setValue(handlebars.compile(tag.attrs['pattern'])(sver), tag);
    latest = true;
    }

    NOTE: It's technically discouraged since SemVer 2.0.0 to prefix with v (but IIRC this is still expected for Go tooling, and this action supports the convention implicitly through the semver package):

    A leading "=" or "v" character is stripped off and ignored. Support for stripping a leading "v" is kept for compatibility with v1.0.0 of the SemVer specification but should not be used anymore. Source

    Using the release event type to trigger does set GITHUB_REF to /refs/tags/<your tag> which is required for this semver tag type:

    metadata-action/src/meta.ts

    Lines 155 to 158 in 8e1d546

    private procSemver(version: Version, tag: tcl.Tag): Version {
    if (!/^refs\/tags\//.test(this.context.ref) && tag.attrs['value'].length == 0) {
    return version;
    }

    But if you have other tag rules like in the referenced issue:

    tags: |
      type=ref,event=branch
      type=ref,event=tag

    That calls different methods to process what tags to generate:

    case tcl.Type.Ref: {
    if (tag.attrs['event'] == tcl.RefEvent.Branch) {
    version = this.procRefBranch(version, tag);
    } else if (tag.attrs['event'] == tcl.RefEvent.Tag) {
    version = this.procRefTag(version, tag);
    } else if (tag.attrs['event'] == tcl.RefEvent.PR) {
    version = this.procRefPr(version, tag);
    }
    break;
    }

    metadata-action/src/meta.ts

    Lines 272 to 286 in 8e1d546

    private procRefBranch(version: Version, tag: tcl.Tag): Version {
    if (!/^refs\/heads\//.test(this.context.ref)) {
    return version;
    }
    const vraw = this.setValue(this.context.ref.replace(/^refs\/heads\//g, ''), tag);
    return Meta.setVersion(version, vraw, this.flavor.latest == 'auto' ? false : this.flavor.latest == 'true');
    }
    private procRefTag(version: Version, tag: tcl.Tag): Version {
    if (!/^refs\/tags\//.test(this.context.ref)) {
    return version;
    }
    const vraw = this.setValue(this.context.ref.replace(/^refs\/tags\//g, ''), tag);
    return Meta.setVersion(version, vraw, this.flavor.latest == 'auto' ? true : this.flavor.latest == 'true');
    }

    As you can see the event=tag will trigger that which just publishes the tag as-is. No v would be stripped then.


    You should be able to use either of the on.release or on.push.tags event triggers since all that matters here is the /ref/tags/<your tag> part of the event, and matching that to the appropriate tag rule.

    As shown earlier, there are only three event types actually supported for type=ref:

    export enum RefEvent {
    Branch = 'branch',
    Tag = 'tag',
    PR = 'pr'
    }

    Thus there is no type=ref,event=release, and neither you nor the comment you referenced shared what tags you configured for the action. You likely either had none and relied upon defaults (which are documented as not stripping the v prefix), or you copy/pasted the tags config from somewhere without understanding them properly.

  2. esune commented on Feb 26, 2025

    @esune

    Left a comment on the original issue as well: #240 (comment)

    It's been a while now, but I believe the issue I was having turned out to be ONLY when creating a pre-release from a tag (i.e.: flagging the release as pre-release in GitHub). Creating official/latest releases seems to work as expected now. I am not sure why pre-releases would behave differently for prefixes though.

  3. polarathene commented on Feb 26, 2025

    @polarathene

    I am not sure why pre-releases would behave differently for prefixes though.

    metadata-action/src/meta.ts

    Lines 156 to 169 in 902fa8e

    if (!/^refs\/tags\//.test(this.context.ref) && tag.attrs['value'].length == 0) {
    return version;
    }
    let vraw: string;
    if (tag.attrs['value'].length > 0) {
    vraw = this.setGlobalExp(tag.attrs['value']);
    } else {
    vraw = this.context.ref.replace(/^refs\/tags\//g, '').replace(/\//g, '-');
    }
    if (!semver.valid(vraw)) {
    core.warning(`${vraw} is not a valid semver. More info: https://semver.org/`);
    return version;
    }

    metadata-action/src/meta.ts

    Lines 171 to 184 in 8e1d546

    let latest = false;
    const sver = semver.parse(vraw, {
    loose: true
    });
    if (semver.prerelease(vraw)) {
    if (Meta.isRawStatement(tag.attrs['pattern'])) {
    vraw = this.setValue(handlebars.compile(tag.attrs['pattern'])(sver), tag);
    } else {
    vraw = this.setValue(handlebars.compile('{{version}}')(sver), tag);
    }
    } else {
    vraw = this.setValue(handlebars.compile(tag.attrs['pattern'])(sver), tag);
    latest = true;
    }

    loose: Be more forgiving about not-quite-valid semver strings. (Any resulting output will always be 100% strict compliant, of course.) For backwards compatibility reasons, if the options argument is a boolean value instead of an object, it is interpreted to be the loose param.

    Strict-mode Comparators and Ranges will be strict about the SemVer strings that they parse.

    • valid(v): Return the parsed version, or null if it's not valid.
    • major(v): Return the major version number.
    • minor(v): Return the minor version number.
    • patch(v): Return the patch version number.
    • parse(v): Attempt to parse a string as a semantic version, returning either a SemVer object or null.

    Source (semver NPM docs)

    That first snippet I reference calls semver.valid(), which if done so as strict-mode presumably emits that warning for pre-release with v? Otherwise if it's not occurring there, we have that 2nd snippet which has a pre-release conditional branch..

    Looks like it'd probably get treated the same as a non-prerelease, although I'm not sure where {{version}} is handled, as those semver docs don't mention a version() function. From what I can see handlebars receives the template to generate the tag string from, and it's given sver as an object with functions on it to call from the template string.

    If the v prefix stripping occurs somewhere else, I'm not quite sure where 🤔 I assume it's reliant upon semver package, so if that doesn't support stripping away the v prefix, the bug fix would either be handled up there or some additional config option for using semver in the referenced snippets above.

  4. polarathene commented on Feb 26, 2025

    @polarathene

    For reference the README type=semver section does suggest that the v prefix should be stripped:

    Image

    Image

    You likely had a misconfiguration that used the raw tag then? Or another rule like the reporter here likely had with a type=ref,event=tag?

    Someone would need to reproduce and share actual workflow config and run if it's happening with only type=semver 🤔

  5. esune commented on Feb 26, 2025

    @esune

    I am pretty positive I was using type=semver,pattern={{version}} and type=semver,pattern={{major}}.{{minor}} in my action. There also is type=ref,event=pr as the tagging happens inside of a build action that is reused in a couple workflows, and the tags might be different based on that (see here if curious).

    I would have to test again and see if the issue persists and what is it exactly, might do it as an experiment next time we need to publish a release by going through a pre-release first).

  6. polarathene commented on Feb 27, 2025

    @polarathene

    There also is type=ref,event=pr as the tagging happens inside of a build action that is reused in a couple workflows, and the tags might be different based on that (see here if curious).

    I went through the tag history of that repo, were these your pre-release style tags in question?:

    Image

    Because they were published without a v prefix:

    Image


    I would have to test again and see if the issue persists and what is it exactly, might do it as an experiment next time we need to publish a release by going through a pre-release first).

    Nothing stood out from the other two tag types when I went over the docker/metadata-action source, and without a reproduction of it in the wild to reference I'd have to attempt to reproduce myself instead of trying to drive-by assist 😝

    If you do encounter it again, a more minimal workflow to reproduce may also be worthwhile context to confirm it's not due to a more complex interaction.


    Off-topic: Out of curiousity, are you getting any known value from using type=sha / type=ref,event=pr (EDIT: This one I understand)?

    I don't see much point in the sha tags personally:

    • Does anyone actually use those tags for your images to reference past images? Generally you'd use an explicit tag and if that tag is prone to being re-assigned you may instead pin by digest at the time.
    • It's messy to browse past images that no longer serve a purpose when they're still in the tagged images list 🤷‍♂ (I'm assuming you chose to do so on the whim that it might be useful before you ever actually had a need for it?)
    Ignore (resolved)

    Same with the PR tagging. Not sure if you're aware but secrets on PR triggered workflow runs are not available (unless using something like pull_request_target event and trusting the PR itself cannot exploit access to secrets that way). IIRC that may not apply to branches on your own repo that PR to another branch, but that doesn't make the feature too useful consistency wise when it comes to third-party contributors. I mention this due to stuff like this which is referencing secrets.

    EDIT: Workflow run for PR on local repo branch. I see you're actually deploying the PR images with a purpose and a conditional to ensure it's only on local branches with a cleanup step (I was wondering why I didn't see many pr tagged images 😅)

  7. esune commented on Feb 27, 2025

    @esune

    @polarathene honestly at this point I don't recall anymore, it's been too long 😅

    For the sha tag: we use the short sha to deploy to our dev environment and know which image is there at any given time, as it is the target of our CI/CD pipeline. I agree all of those sha tags though make the artifact repository messy: it is something that was inherited and we never managed to fully clean-up, we had an action that was supposed to clean up old/unused images (snok/container-retention-policy), but after an update it started deleting more than it needed to and we had to turn it off

  8. polarathene commented on Jan 18, 2026

    @polarathene

    This issue can be closed.

    The following results in the same tags produced by a release event or manual action trigger with equivalent tag value as an input.

    name: 'Build and Publish the container image'
    
    on:
      release:
        types:
          - published
    
      workflow_dispatch:
        inputs:
          version:
            description: 'Version to build (e.g., 1.42.0)'
            required: true
            type: string
    
    jobs:
      container:
        runs-on: ubuntu-latest
        permissions:
          contents: read
          packages: write
        steps:
          - name: Prepare image metadata
            id: metadata
            uses: docker/metadata-action@v5
            with:
              images: |
                ghcr.io/${{ github.repository }}
              tags: |
                type=semver,pattern={{major}}.{{minor}}.{{patch}}
                type=semver,pattern={{major}}.{{minor}}
                type=semver,pattern={{major}}
    
          - name: Set up Docker Buildx
            uses: docker/setup-buildx-action@v3
    
          - name: Log in to Container Registry
            uses: docker/login-action@v3
            with:
              registry: ghcr.io
              username: ${{ github.repository_owner }}
              password: ${{ secrets.GITHUB_TOKEN }}
    
          - name: Build and push container image
            uses: docker/build-push-action@v6
            env:
              SOURCE_DATE_EPOCH: 0
            with:
              provenance: false
              push: true
              platforms: linux/amd64,linux/arm64
              tags: ${{ steps.metadata.outputs.tags }}
              outputs: type=image,oci-mediatypes=true,oci-artifact=true,rewrite-timestamp=true

    I tested with publishing releases with v1.2.3 with the latest and pre-release options enabled (neither option influenced the outcome), and also a v1.2.3-alpha (pre-release semver, which the action detects and will ignore the major.minor + major release tags). In all cases the v prefix was stripped.

    NOTE:

    • For the workflow_dispatch trigger, you would need modify the workflow to explicitly provide the value via appending:

      • ,value=${{ inputs.version }}
      • ,value=${{ inputs.version || github.ref_name }} (compatible fallback that still works with release event)
    • For those that want to prefix the version with a v, you could do so with:

      • type=raw (needs explicit value provided, skips any semver logic and thus implicit latest tag triggered via default flavor.latest setting)
      • type=semver,pattern={{ raw }} (no stripping of tag value, just needs to be valid semver)
      • type=semver,pattern=v{{ major }}.{{ minor }} (works but is discouraged)
      • type=semver,pattern={{ major }}.{{ minor }},prefix=v (better, more consistent/deterministic if pre-release is involved)

      type=semver parses the tag into a semver value, which strips that v. To retain the v prefix, you can prefix to the pattern expression like shown above, but this is problematic as explained below. Better to go with prefix=v.

      For pre-release detected semver values, the actions logic only permits type=semver,pattern={{ raw }} with no customizations in pattern, otherwise it will override any other pattern value into pattern={{ version }}.

      • If you use v prefixed tags as input, these will only retain the v prefix via the raw pattern (as communicated via the READEME type=semver table).
      • If your tag input lacks a v prefix and you want one on the tag only, just use the prefix=v option (better solution overall).
      • Be mindful of this pre-release behaviour if permitting such semver tags to be published, otherwise {{ raw }} usage would retain the v prefix, but the "skipped" major and major+minor patterns would be {{ version }} patterns that strip the v prefix, resulting in two tags. As stated it's better to use prefix=v instead.
  9. inaku-Gyan commented on Jan 24, 2026

    @inaku-Gyan

    For pre-release detected semver values, the actions logic only permits type=semver,pattern={{ raw }} with no customizations in pattern, otherwise it will override any other pattern value into pattern={{ version }}.

    • If you use v prefixed tags as input, these will only retain the v prefix via the raw pattern (as communicated via the READEME type=semver table).
    • If your tag input lacks a v prefix and you want one on the tag only, just use the prefix=v option (better solution overall).
    • Be mindful of this pre-release behaviour if permitting such semver tags to be published, otherwise {{ raw }} usage would retain the v prefix, but the "skipped" major and major+minor patterns would be {{ version }} patterns that strip the v prefix, resulting in two tags. As stated it's better to use prefix=v instead.

    Hi, may you add this clarification into the README file? This is really confusing. I really wasted a lot of time on this before I found this issue. I think a more detailed README can save people's time.

  10. polarathene commented on Jan 24, 2026

    @polarathene

    Would adding just a link to that quoted comment work?

    The README is already quite long in some areas, and already does try to communicate the behaviour with pre-release values.

    metadata-action/README.md

    Lines 507 to 534 in ed95091

    `pattern` attribute supports [Handlebars template](https://handlebarsjs.com/guide/)
    with the following expressions:
    * `raw` ; the actual tag
    * `version` ; shorthand for `{{major}}.{{minor}}.{{patch}}` (can include pre-release)
    * `major` ; major version identifier
    * `minor` ; minor version identifier
    * `patch` ; patch version identifier
    | Git tag | Pattern | Match | Output |
    |------------------|-----------------------|----------------|------------------|
    | `v1.2.3` | `{{raw}}` | | `v1.2.3` |
    | `v1.2.3` | `{{version}}` | | `1.2.3` |
    | `v1.2.3` | `{{major}}.{{minor}}` | | `1.2` |
    | `v1.2.3` | `v{{major}}` | | `v1` |
    | `v1.2.3` | `{{minor}}` | | `2` |
    | `v1.2.3` | `{{patch}}` | | `3` |
    | `p1/v1.2.3` | `{{version}}` | `v(\d.\d.\d)$` | `1.2.3` |
    | `v2.0.8-beta.67` | `{{raw}}` | | `v2.0.8-beta.67` |
    | `v2.0.8-beta.67` | `{{version}}` | | `2.0.8-beta.67` |
    | `v2.0.8-beta.67` | `{{major}}` | | `2.0.8-beta.67`* |
    | `v2.0.8-beta.67` | `{{major}}.{{minor}}` | | `2.0.8-beta.67`* |
    > [!IMPORTANT]
    > *Pre-release (rc, beta, alpha) will only extend `{{version}}` (or `{{raw}}`
    > if specified) as tag because they are updated frequently, and contain many
    > breaking changes that are (by the author's design) not yet fit for public
    > consumption.

    About the only bit of information not covered in the README is that when you want to:

    • Keep the v prefix, you can choose prefix=v{{ version }} / pattern=v{{ major }} / pattern=v{{ major }}/{{ minor }} which all would become pattern={{ version }} when the value is a pre-release semver version.
    • Add the v prefix, pattern=v{{ raw }} might seem valid according to the current docs, but it will also be turned into pattern={{ version }}, only pattern={{ raw }} (no prefix/suffix supported in pattern, but prefix=/suffix= options are fine) will retain the raw value when it's detected as a pre-release semver value (which occurs before detecting pattern={{ raw }} usage).

    The docs don't suggest using pattern=v{{ ... }} so I'm not sure how useful that is to actually add? It's just something you may do from experimenting and think "Hey this is working!" and then only later realize the caveat once you publish a pre-release semver tag.


    The pattern={{ raw }} caveat could be deemed a bug technically, and should permit the prefix/suffix.

    I assume it's because you're not really meant to use pattern with customizations beyond the . delimiters (like expressing a pattern to match, which is how I originally interpreted the setting (if this pattern matches, then create a tag with this pattern), but instead the condition is only on the input value being valid semver and if it is a pre-release semver).

    A fix would instead be to ensure that only the raw expression was present, while allowing prefix/suffix values that aren't other expressions.

  11. inaku-Gyan commented on Jan 25, 2026

    @inaku-Gyan

    I think a link would also be fine!

    It's just something you may do from experimenting and think "Hey this is working!" and then only later realize the caveat once you publish a pre-release semver tag.

    This can be quite dangerous because the issue stays hidden until pre-release is involved. People should be made aware of it. I guess I was lucky to have tested my workflow with a pre-release tag from the start, so I caught the problem early.

  12. polarathene commented on Jan 25, 2026

    @polarathene

    People should be made aware of it. I guess I was lucky to have tested my workflow with a pre-release tag from the start, so I caught the problem early.

    Yes I understand, I also test early for such as it's not documented officially anywhere to use v prefix in pattern (EDIT: My mistake, it actually is in the conversion table example 😬) so that is kind of a risk of the user when they should be preferring the documented prefix option to add the v prefix if that is what they'd like.

    This issue in particular was about v apparently not being stripped, which as my earlier comment documented was either a bug in the past or misconfiguration at the users workflow (that wasn't shared at the time). Presently the docs cover the stripping behaviour, including with pre-release semver tags and what to expect from the different pattern inputs.


    I can contribute a link to one of the above comments, but would appreciate some context on what I should be highlighting to the user when describing the link.

    I will post a comment after this one that better details with examples of two scenarios:

    1. No v prefix from tagged commit, add v prefix on generated image tag.
    2. v prefix from tagged commit, keep v prefix on generated image tag.

    Let me know if that is the topic you wanted to have documented better and if so I'll open a PR to link to that comment to increase awareness.

  13. polarathene commented on Jan 25, 2026

    @polarathene

    Caveats and fixes for the tags input with semver tags and adding/retaining a v prefix on the image tag

    NOTE: AFAIK it's generally discouraged to have a v prefix for your semver image tags.

    • It is however a common practice for git tags which can better help filter/isolate from other git tags, I know it's also expected for Go tooling and perhaps elsewhere in the wild too.

    • I cited this earlier in the issue discussion:

      NOTE: It's technically discouraged since SemVer 2.0.0 to prefix with v (but IIRC this is still expected for Go tooling, and this action supports the convention implicitly through the semver package):

      A leading "=" or "v" character is stripped off and ignored. Support for stripping a leading "v" is kept for compatibility with v1.0.0 of the SemVer specification but should not be used anymore. Source

    Despite this, if you still intend to keep the v prefix for the image tags you publish, there are some caveats to be mindful of, especially if your workflow will run for pre-release semver tags (which as described below can be correctly handled to avoid surprises).

    Semver tag input does not have v prefix (but I want my image tag to have v prefix)

    A user might consider to experiment with pattern (like I did with pattern=v{{ raw }}, or based on the docs patten=v{{ major }} example) and then discover unexpected results with pre-release tags like I've detailed regarding only pattern={{ raw }} is exempt from pre-release tags being changed to pattern={{ version }}.

    For example, take this tags input:

    tags: |
      pattern=v{{ version }}
      pattern=v{{ major }}.{{ minor }}
      pattern=v{{ major }}

    For 1.2.3 tag for value= (or implicit via git ref):

    v1.2.3
    v1.2
    v1
    

    For the equivalent pre-release tag 1.2.3-alpha (all patterns are changed into pattern={{ version }} and duplicates removed):

    1.2.3-alpha
    

    No v prefix due to conversion (which stripped away the v in pattern). Instead switch to using prefix= like this:

    tags: |
      pattern={{ version }},prefix=v
      pattern={{ major }}.{{ minor }},prefix=v
      pattern={{ major }},prefix=v

    For a pre-release these all have their pattern set to {{ version }}, but prefix is left unmodified (so the v prefix will actually be prepended):

    v1.2.3-alpha
    

    Semver tag input already has v prefix (and I want to retain it on my image tag)

    pattern={{ major }} / pattern={{ major }}.{{ minor }} aren't actually skipped for pre-release (as the note on pre-release tags might imply) but converted to pattern={{ version }} (with any v stripped), but as the docs conversion table illustrates if you use pattern={{ raw }} (instead of choosing the similar pattern={{ version }} / pattern={{ major }}.{{ minor }}.{{ patch }}) for representing the full/original semver tag (regular or pre-release semver), then this would retain the v prefix (as expected from {{ raw }}) and you'd have tags produced like so:

    tags: |
      pattern={{ raw }}
      pattern={{ major }}.{{ minor }}
      pattern={{ major }}

    With a semver tag v1.2.3 for value= (or implicit via git ref):

    v1.2.3
    1.2
    1
    

    However with a pre-release tag input (both major.minor and major patterns are changed into pattern={{ version }} and the extra duplicate removed):

    v1.2.3-alpha
    1.2.3-alpha
    

    Instead, you would want to prefer this to retain the v prefix of an input tag (like v1.2.3) on the last two patterns:

    tags: |
      pattern={{ raw }}
      pattern={{ major }}.{{ minor }},prefix=v
      pattern={{ major }},prefix=v

    which fixes v1.2.3 (just like pattern=v{{ major }}.{{ minor }} could):

    v1.2.3
    v1.2
    v1
    

    and now for the pre-release (since all duplicates are discarded), we have the expected result:

    v1.2.3-alpha
    
  14. inaku-Gyan commented on Jan 26, 2026

    @inaku-Gyan

    Thanks for the clarifications and for improving the documentation.
    The two scenarios you outlined are exactly what I think users should be aware of. Please go ahead and open a PR for this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions