Automate Releases with Semantic Release and GitHub Actions (Step-by-Step)



This content originally appeared on DEV Community and was authored by OctoLab Team

One of the most common uses of GitHub Actions in Node.js projects is to automate releases: calculate the next version, tag the repository, generate release notes and publish a GitHub Release (and optionally publish to NPM).

This release flow usually involves a few key steps:

  • Install dependencies to ensure the project can build.
  • Build the project if your package/app requires compiled artifacts.
  • Run Semantic Release to analyze commits, bump the version (major/minor/patch), create a tag and publish the release notes.

In this article we will set up a GitHub Actions workflow that automates this process every time you push to main. We will explain step by step what each part of the workflow does and why it is important.

🎯 The purpose of the workflow

We want that, every time a push happens in our main branch, a set of tasks runs to automate releases using Conventional Commits:

  • Analyzes commit messages and determines the next version (major/minor/patch)
  • Creates a Git tag and generates release notes
  • Publishes a GitHub Release
  • (Optional) Publishes to npm if configured via plugins

🧬 General workflow structure

name: Releasing with Semantic Release

on:
  push:
    branches:
      - main

jobs:
  release:
    name: release
    runs-on: ubuntu-latest
    steps:
      - id: checkout-step
        name: Checkout code
        uses: actions/checkout@v4

      - id: setup-node-step
        name: Setup Node
        uses: actions/setup-node@v4
        with:
          node-version: "22"

      - id: install-dependencies-step
        name: Install dependencies
        run: npm install

      - id: build-step
        name: Build package
        run: npm run build

      - id: semantic-release-step
        name: Release with Semantic Release
        env:
          GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}
        run: npx semantic-release

⚙ What parts compose a GitHub Actions workflow?

Before going into detail, it is useful to understand how a GitHub Actions workflow is structured. The main elements are:

Trigger (on:)
Defines when the workflow is executed. It can be when pushing, opening a pull request, publishing a release, executing manually, etc.
In our case, we use push to main, which triggers a release attempt every time new commits land in the main branch.

Job (jobs:)
Each workflow can have one or several jobs. A job is a set of tasks that are executed in the same environment.
All the steps of a job share the same filesystem and context. In our example, we have a single job called release.

Runner (runs-on:)
It is the operating system where the job is executed. GitHub provides hosted runners, such as ubuntu-latest, windows-latest or macos-latest, but we also have the possibility to use self-hosted runners.

Steps (steps:)
These are the actions or commands that are executed within a job. They can be:

  • Reusable actions such as actions/checkout or actions/setup-node.
  • Specific commands defined with run, such as npm install or npx semantic-release.

🪜 Step-by-step of our workflow

Now that we understand how a workflow is structured, let’s analyze each of the steps that make up our workflow. We will see what each action does, why it is necessary and what additional configurations we could apply depending on the case.

on: push (branches: main)

This block indicates that the workflow will be triggered when you push to main. Semantic Release will then decide whether a new release should be created based on the commit history (using Conventional Commits).

uses: actions/checkout@v4

This step downloads the code from the repository so that it is available within the runtime environment.

Some additional options that this action allows are:

  • fetch-depth: 0 clones the entire repository history (by default only the current commit). Semantic Release may benefit from full history in some setups.
  • ref: branch-name** you can set a specific branch
  • path: subdirectory clone the code in a specific path

For this case, just use it without extra configuration:

- uses: actions/checkout@v4

uses: actions/setup-node@v4

This action installs a specific version of Node.js, in our case version 22. It also allows you to configure dependency caching and more.

- uses: actions/setup-node@v4
  with:
    node-version: "22"

Other useful options are:

  • cache: ‘npm’ enables dependency caching using actions/cache underneath
  • check-latest: true** forces to use the latest available version that complies with the semver.

Working scripts

- run: npm install
- run: npm run build
- run: npx semantic-release

These steps execute the commands defined in your project and run Semantic Release to automate the versioning and release process:

  • npm install will install all the project’s dependencies.
  • npm run build** will generate the final package or artifacts if your project needs a build step (libraries, apps, compiled bundles, etc.).
  • npx semantic-release will: — analyze commits following Conventional Commits, — determine the next version (major/minor/patch), — create a Git tag, — generate release notes, and — publish a GitHub Release (and optionally publish to npm if configured via plugins).

💡 Token & config tips

  • Expose a token with permissions to create tags/releases: env: { GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} } You can also use the default GITHUB_TOKEN with proper write permissions.
  • Provide a Semantic Release configuration (in package.json under release or in a .releaserc file) with the plugins you need (e.g., @semantic-release/github, @semantic-release/changelog, @semantic-release/npm, @semantic-release/git).
  • Ensure your commit messages follow Conventional Commits so Semantic Release can infer the correct next version.

✅ Final result

With this flow you guarantee that every push to main goes through an automated release process, without manual tagging or copy-pasting release notes. You can integrate it with additional plugins/steps such as:

  • Automatic CHANGELOG.md updates and commits
  • npm publishing with @semantic-release/npm
  • Multi-branch release strategies (e.g., next, beta)
  • Notifications (Slack, Discord, etc.)

🐙 Do you want to avoid writing it by hand? There is a more convenient way

Understanding how a workflow works in GitHub Actions is indispensable. It gives you control and helps you customize everything to your needs.

Now… if you don’t feel like struggling with the syntax, that’s fine too.
At OctoLab we offer you the ability to set up this same flow visually: you choose the actions, fill in the fields and it generates the YAML for you on the fly.

✅ Without indentation errors
✅ With built-in validations
✅ Copy, paste and go.

🧠 Conclusion

This workflow is an excellent basis for automated, reliable releases in Node.js projects. It can be easily adapted to other needs: publishing to npm, updating changelogs automatically, multi-branch strategies, or running on multiple Node versions.

If you prefer to use OctoLab we will be very grateful for your feedback! 🤗


This content originally appeared on DEV Community and was authored by OctoLab Team