Skip to content

Releases on deploy – setup

What it does

When deploying to production (master, main or production), the workflow automatically:

  1. Creates a tag on the deployed commit, e.g. v2026.09.11.1
  2. Creates a GitHub release with that tag, titled v2026.09.11.1 — production
  3. Fills the release notes with the PRs merged since the previous release

The Releases page in the project repo then answers, at a glance, what is live, when it went out, and what it contained. Nothing is created for development deploys.


Versioning

Tags use the date of the deploy plus a counter for that day:

v2026.09.11.1    first production deploy on 11 September 2026
v2026.09.11.2    second deploy the same day
v2026.09.12.1    counter resets the next day

The date is UTC, so it matches the timestamps shown on the workflow run. There is nothing to bump manually – the counter is derived from the tags already in the repo.


Setup

1. Allow the workflow to create releases

The release is created with the built-in GITHUB_TOKEN, which needs write access to repository contents. A reusable workflow can never have more permissions than the job calling it, so this has to be granted on the calling job:

  release:
    permissions:
      contents: write

If this is missing, the step fails with a 403 even though the deploy itself succeeded. Alternatively, set Settings → Actions → General → Workflow permissions to Read and write permissions for the repo.

2. Call create-release in your deploy workflow

Add it as a job that runs after the deploy has succeeded:

name: Deploy Nuxt

on:
  push:
    branches:
      - master

jobs:
  deploy:
    uses: subscribed-aps/workflows/.github/workflows/deploy-nuxt.yml@main
    with:
      environment: master
    secrets: inherit

  release:
    needs: deploy
    if: needs.deploy.result == 'success'
    permissions:
      contents: write
    uses: subscribed-aps/workflows/.github/workflows/create-release.yml@main
    with:
      environment: master

The environment input is the same value passed to the deploy workflow, so a workflow_dispatch that can pick between development and production works without extra conditions – the release job simply does nothing when the environment is not a production one.

3. Combine with Asana notifications

If the repo already calls notify-asana, both jobs can hang off the same deploy. The order does not matter:

  notify-asana:
    needs: deploy
    if: needs.deploy.result == 'success'
    uses: subscribed-aps/workflows/.github/workflows/notify-asana.yml@main
    with:
      environment: master
      base_sha: ${{ github.event.before }}
    secrets:
      ASANA_TOKEN: ${{ secrets.ASANA_TOKEN }}

  release:
    needs: deploy
    if: needs.deploy.result == 'success'
    permissions:
      contents: write
    uses: subscribed-aps/workflows/.github/workflows/create-release.yml@main
    with:
      environment: master

As a bonus, notify-asana falls back to the most recent tag when no base_sha is given. Once these releases exist, that fallback becomes an exact "since the last deploy" range instead of a guess at the last 20 commits.


Optional parameters

Input Default Description
environment Required Environment deployed. The release is only created for master, main or production
tag_prefix v Prefix for the generated tag. Use to namespace tags in a monorepo, e.g. api-v
sha Current commit Commit to tag. Only needed if the release should point somewhere other than the commit that triggered the workflow

Outputs

Output Description
tag The tag that was created, e.g. v2026.09.11.1
url URL of the created release

Error handling

Unlike the Asana notification, a failed release does fail the job. The release log is only useful if it is complete, so a missing release should be visible rather than silently swallowed. The job runs after the deploy, so a failure here never blocks or rolls back the deploy itself.

If two production deploys land at nearly the same time, a concurrency group serialises them so they do not claim the same tag number. Should it happen anyway, the workflow refreshes its tags and retries once.