Official blog of Daniel Uhlmann

Automating Builds and Releases with Conventional Commits and Semantic Versioning

With multiple repositories where I manage package builds and releases through GitHub pipelines, I sought a reusable solution that could streamline my workflows across all repositories. Additionally, since I now use Conventional Commits across all the repositories I maintain, I wanted a solution that incorporates this convention into the release workflow. In this blog post, I’ll share my approach and provide an example of how to automatically create semantic versioned releases and generate changelogs based on your Conventional Commits.

The approach that I’ve implemented is powered by semantic-release , which automatically handles versioning based on Conventional Commits . By analyzing the commit history, it determines whether a release should be major, minor, or patch. The workflow then builds and tags a Docker image, and pushes it to GitHub Container Registry (GHCR).

First, we need a trigger

I set up a GitHub Action that triggers two events:

Here’s how the trigger section looks:

1name: Release and Build our Docker Image
2
3on:
4  push:
5    branches: - main
6  workflow_dispatch:

Use semantic-release for Automated Versioning

The first job in this workflow handles the release process. The core of this job is semantic-release , which automates versioning based on the commit messages. This ensures that every change is tagged correctly following Semantic Versioning . Semantic-release also updates the changelog and pushes the new release details to GitHub. The jobs need some specific permissions so grant them carefully.

Here is the configuration part we need:

 1jobs:
 2  release:
 3    runs-on: ubuntu-latest
 4
 5    permissions:
 6      contents: write
 7      packages: write
 8
 9    steps:
10      - name: Checkout the repository
11        uses: actions/checkout@v4
12        with:
13          fetch-depth: 0

In addition to checking out the repository, semantic-release requires access to your repo’s history to analyze commits and generate the next version.

Next, I set up semantic-release by running it as part of the CI process:

1- name: Run Semantic Release
2  env:
3    GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
4  run: npx semantic-release

This ensures that every valid commit triggers a new release. Based on the commit messages, semantic-release determines the correct version (major, minor, or patch), tags the release, and updates the GitHub repository with the latest version.

Configure semantic-release in Your Repository

On top of the GitHub Action configuration, we need some configuration for semantic-release itself in our repository.

A good starting point to install all the required packages in your own codebase would be: https://github.com/semantic-release/semantic-release/blob/master/docs/usage/installation.md

releaserc.yml Configuration

We need this file to configure semantic-release options or plugins that we need. To control how semantic-release operates, I used the following plugins in a releaserc.yml file:

1branches:
2  - main
3
4plugins:
5  - "@semantic-release/commit-analyzer"
6  - "@semantic-release/release-notes-generator"
7  - "@semantic-release/changelog"
8  - "@semantic-release/github"
9  - "@semantic-release/git"

Each plugin serves a specific purpose:

The official documentation is very well written if you want to implement additional things.

Building and Pushing Docker Images

Once the release job is completed successfully, the next step is to build and push a Docker image to GHCR (GitHub Container Registry). This job depends on the release job, ensuring it only runs when a new release is created.

1build-and-push-image:
2  needs: release
3  runs-on: ubuntu-latest
4  if: success()

Docker Login

Before building the image, I log in to the GitHub Container Registry using a GitHub token. This provides the necessary credentials for pushing the image later.

1- name: Log in to the Container registry
2  uses: docker/login-action@v3
3  with:
4    registry: ghcr.io
5    username: ${{ github.actor }}
6    password: ${{ secrets.GITHUB_TOKEN }}

Fetching the Latest Git Tag

To ensure that the Docker image is tagged correctly, I retrieve the latest Git tag generated by semantic-release:

1- name: Get latest Git tag
2  id: version
3  run: |
4    latest_tag=$(git describe --tags `git rev-list
5                 --tags --max-count=1`)
6    echo "Latest Git tag: $latest_tag"
7    echo "version=$latest_tag" >> $GITHUB_ENV

This step stores the latest version tag in the GitHub environment for use in subsequent steps.

Note: This is a trivial approach on how to get the latest tag from your repository history. You might need to change this part of the pipeline to fit your needs.

Setting Docker Image Tags

I use the docker/metadata-action to set the image tags. This allows me to tag the image with both latest and the newly created version.

1- name: Set Docker meta
2  id: meta
3  uses: docker/metadata-action@v5
4  with:
5    images: ghcr.io/${{ github.repository }}
6    tags: |
7      type=raw,value=latest
8      type=raw,value=${{ env.version }}

Building and Pushing the Docker Image

Finally, the Docker image is built and pushed to GHCR using the following step:

1- name: Build and push Docker image
2  uses: docker/build-push-action@v6
3  with:
4    context: .
5    push: true
6    tags: ${{ steps.meta.outputs.tags }}

This command builds the Docker image based on the latest code and pushes it to the registry with the specified tags (latest and the specific version).

✨ The full GitHub Action can be found here: https://gist.github.com/xFuture603/4933f23cbe59fe9eba5fc832e84e4ada

Conclusion

By integrating Conventional Commits, and semantic-release, I’ve streamlined the process of building and releasing container images. Now, every push to the main branch triggers an automatic version bump, release, and container image build, ensuring consistency and reducing manual overhead.

Sources

Conventional Commits - https://www.conventionalcommits.org/en/v1.0.0/

Semantic Release Configuration File Documentation - https://semantic-release.gitbook.io/semantic-release/usage/configuration#configuration-file

Working with Conventional Commits - https://xfuture-blog.com/working-with-conventional-commits/

Semantic Versioning - https://semver.org/

semantic-release - https://github.com/semantic-release/semantic-release

GitHub Action Pipeline Code - https://gist.github.com/xFuture603/4933f23cbe59fe9eba5fc832e84e4ada

<< Previous Post

|

Next Post >>