Using GitHub Actions to Automate Releases and Changelogs

Team 5 min read

#github-actions

#ci-cd

#release-management

#changelog

Introduction

Automating releases and changelogs saves time and reduces human error. With GitHub Actions, you can trigger releases when you tag a new version, generate a new changelog automatically, and publish assets or packages to registries. This post walks through practical patterns, from a simple tag-based release to a full end-to-end workflow powered by semantic release.

Prerequisites

  • A repository with a conventional commits or clear commit messages to drive changelog generation.
  • Access to push to the repository (GitHub token available to Actions by default).
  • A package.json or project setup that you want to release (optional for non-NPM projects).
  • Basic familiarity with GitHub Actions and YAML workflows.

What you can automate

  • Create a GitHub Release when a new tag is pushed.
  • Generate or update a CHANGELOG.md automatically.
  • Optionally publish artifacts (e.g., binaries, NPM packages) as part of the release.
  • Keep release notes aligned with your commit history or conventional commit messages.

Option 1: Tag-based release with a generated CHANGELOG

This approach uses a tag to trigger a release and updates a CHANGELOG.md entry as part of the release process.

Example workflow: release on tag

Create a file at .github/workflows/release-on-tag.yml with the following content:

name: Release on Tag

on:
  push:
    tags:
      - 'v*.*.*'

jobs:
  release:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v3

      - name: Set up Node
        uses: actions/setup-node@v4
        with:
          node-version: '18'

      - name: Install dependencies
        run: npm ci

      - name: Generate CHANGELOG
        run: npx conventional-changelog -p angular -i CHANGELOG.md -s && git diff --quiet
      - name: Commit CHANGELOG
        run: |
          git config user.name "github-actions[bot]"
          git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
          git add CHANGELOG.md
          if git diff --cached --quiet; then
            echo "CHANGELOG.md is up to date"
          else
            git commit -m "docs(changelog): update CHANGELOG for ${GITHUB_REF_NAME}"
            git push
          fi
      - name: Create Release
        uses: actions/create-release@v1
        with:
          tag_name: ${{ github.ref_name }}
          release_name: Release ${{ github.ref_name }}
          draft: false
          prerelease: false
      - name: Upload Release Asset (optional)
        uses: actions/upload-release-asset@v1
        with:
          upload_url: ${{ steps.create_release.outputs.upload_url }}
          asset_path: dist/my-app.zip
          asset_name: my-app.zip
          asset_content_type: application/zip

Notes:

  • The CHANGELOG generation uses conventional-changelog with the angular preset as a common convention. You can adjust the preset or rules to fit your project.
  • The asset upload step is optional and only needed if you build and publish artifacts with the release.

Option 2: Automatic releases and changelog with semantic-release

For a fully automated approach, use semantic-release to determine the next version from commit messages, update CHANGELOG.md, commit changes, and create the GitHub release.

Prerequisites for semantic-release

  • Install semantic-release and required plugins (or run via npx).
  • A .releaserc.json (or release configuration) specifying your changelog destination and plugins.
  • Conventional commits or a similar scheme to drive version bumps.

Example release configuration

Create a file named .releaserc.json with:

{
  "branches": ["main"],
  "plugins": [
    "@semantic-release/commit-analyzer",
    "@semantic-release/release-notes-generator",
    ["@semantic-release/changelog", {
      "changelogFile": "CHANGELOG.md"
    }],
    ["@semantic-release/git", {
      "assets": ["CHANGELOG.md"],
      "message": "chore(release): ${nextRelease.version} [skip ci]"
    }],
    "@semantic-release/github"
  ]
}

Example workflow: end-to-end semantic release on tag

Create .github/workflows/release-semantic.yml:

name: Semantic Release

on:
  push:
    tags:
      - 'v*.*.*'

jobs:
  release:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v3

      - name: Setup Node
        uses: actions/setup-node@v4
        with:
          node-version: '18'

      - name: Install dependencies
        run: npm ci

      - name: Run semantic-release
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: npx semantic-release

How this works:

  • semantic-release analyzes your commit messages to decide the next version (major, minor, patch).
  • It updates CHANGELOG.md via the changelog plugin.
  • It commits the changelog and version changes back to the repository.
  • It creates a GitHub Release via the GitHub plugin.

Tips for success:

  • Use conventional commits (feat, fix, perf, docs, chore, etc.) to drive sensible version bumps.
  • Ensure your workflow has permission to push changes (GITHUB_TOKEN with contents: write).
  • If you publish packages (e.g., npm), extend the plugins with @semantic-release/npm or similar.

Option 3: Changelog drafts with Release Drafter (synthetic approach)

If you prefer drafting release notes in advance via pull requests, Release Drafter can help populate a draft release body as PRs are merged.

Setup highlights

  • Add a draft release config at .github/release-drafter.yml.
  • Optionally run a lightweight workflow that creates a release when you push a tag, using Release Drafter to compose the body.

Sample Release Drafter config (.github/release-drafter.yml):

name-template: 'Release ${tag_name}'
tag-template: '${tag_name}'
categories:
  - title: 'Features'
    label: features
  - title: 'Bug Fixes'
    label: fixes
  - title: 'Other Changes'
    label: other

And a minimal workflow to pair with it (optional):

name: Draft Release Notes

on:
  push:
    branches: [ main ]

jobs:
  draft:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: release-drafter/release-drafter@v5
        with:
          config-name: release-drafter-config

Note: Release Drafter helps generate a draft release body but does not overwrite CHANGELOG.md by default. You can combine it with a separate changelog generation step if you want a CHANGELOG.md in addition to the draft.

Best practices and tips

  • Choose a strategy that matches your workflow: simple tag-based releases for straightforward projects, or semantic-release for true automation tied to commit messages.
  • Document your release process in CONTRIBUTING.md so contributors understand how releases are produced.
  • Use a changelog file (CHANGELOG.md) as the single source of truth for consumers who don’t use the GitHub Releases page.
  • If you publish artifacts, consider including checks or tests in the release workflow to ensure the artifact is stable before publishing.
  • Keep your workflows versioned alongside your code to avoid sudden breaking changes in CI.

Conclusion

GitHub Actions makes it practical to automate both releases and changelogs, from tag-triggered releases to fully automated version bumps and changelog updates. By selecting a strategy that fits your project’s needs—tag-based with a generated changelog, full semantic-release automation, or a draft-notes approach—you can release faster and more reliably while keeping stakeholders informed.