GitHub Actions Release Automation

A release marks a stable version of your software that users can download. Creating releases by hand invites mistakes: forgotten files, missing notes, wrong version labels. A workflow handles the same job in seconds and repeats it perfectly every time.

Release Automation in Simple Words

Think of a bakery that stamps each loaf with a batch number and ships it in a labeled box. The baker only decides when a batch is ready. The packaging line does the rest. A tag is the baker's decision, and the workflow is the packaging line.

Version Tags

A Git tag is a permanent label on a specific commit. Teams usually follow semantic versioning, which uses three numbers.

v 2 . 5 . 1
  |   |   |
  |   |   +-- Patch: bug fixes only
  |   +------ Minor: new features, nothing breaks
  +---------- Major: changes that may break old usage

The Release Pipeline

+------------+     +-------------+     +-----------+     +----------------+
| Push tag   | --> | Build and   | --> | Package   | --> | Create GitHub  |
| v1.2.0     |     | test code   |     | files     |     | Release + notes|
+------------+     +-------------+     +-----------+     +----------------+

Trigger on Tag Push

A tag filter starts the workflow only for version tags.

on:
  push:
    tags:
      - "v*"

The pattern v* matches v1.0.0, v2.3.4, and any other tag that starts with the letter v.

Complete Workflow

name: Release

on:
  push:
    tags:
      - "v*"

permissions:
  contents: write

jobs:
  release:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Build project
        run: |
          mkdir dist
          echo "Build for ${{ github.ref_name }}" > dist/info.txt
          zip -r app-${{ github.ref_name }}.zip dist

      - name: Create release
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          gh release create "${{ github.ref_name }}" \
            app-${{ github.ref_name }}.zip \
            --title "Version ${{ github.ref_name }}" \
            --generate-notes

Key Details Explained

Permission

The contents: write scope allows the token to create releases. Read access fails with a permission error.

The ref_name Value

For a tag push, github.ref_name holds the tag text, such as v1.2.0. The workflow uses it in file names and titles.

Generated Notes

The --generate-notes flag collects merged pull requests since the previous release and writes a summary. Contributors receive credit automatically.

Attached Files

Each file path after the tag becomes a downloadable asset on the release page.

Publish a Tag

Run these two commands on your computer to start the pipeline.

git tag v1.2.0
git push origin v1.2.0

Open the Actions tab to watch the run. The Releases section on the repository home page shows the new version when the job completes.

Pre-Releases and Drafts

FlagEffect
--prereleaseMarks the release as not production-ready
--draftSaves the release for review before the public sees it
--latestPins the release as the latest stable version

A draft step works well with environments: the workflow creates a draft, a reviewer reads the notes, and publishing happens manually.

Running Tests First

Add a test job and make the release job wait for it with needs. A failing test blocks the release, so broken code never reaches users.

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm ci
      - run: npm test

  release:
    needs: test
    runs-on: ubuntu-latest
    steps:
      - run: echo "Safe to release"

Good Habits

  • Use tags that start with v and follow semantic versioning.
  • Protect the main branch so only reviewed code gets tagged.
  • Store checksums next to downloads so users can verify files.
  • Keep release notes readable with clear pull request titles.

Consistent tags and clear titles turn the automatic notes into a useful changelog that users can trust.