For having a good overview of changes in the software lifecycle, it makes sense to use a software revision system like GIT to automatically generate a CHANGELOG. In order to do that, it is necessary to write appropriate commit messages.

Below you can find a sample configuration for GitLab CI/CD to automatically generate a file on a push to the main branch. Also, an automatically versioning is implemented, based on Semantic Versioning.

Semantic Versioning

For semantic versioning, a version number consists mainly of 3 digits: MAJOR.MINOR.PATCH. Each part of the version has a different meaning:

  • MAJOR: An increased MAJOR means, that changes are incompatible with previous versions (breaking changes)
  • MINOR: New features were implemented, but all are compatible with previous versions.
  • PATCH: There are only bug fixes and no new features

Also, there are some suffixes for pre-releases, etc. For details, you can read the specification.

Commit Convention

In order to automatically generate a CHANGELOG, it is necessary to have a Commit-Message-Convention. In our example, we will use the Conventional Commits. Obviously, there are other conventions too. The following setup is supporting some of them too – but you can also configure your own conventions.

The key facts of the Conventional Commits specifications are:

The commit message should be structured as follows:

<type>[optional scope]: <description>

[optional body]

[optional footer(s)]

The commit contains the following structural elements, to communicate intent to the consumers of your library:

  1. fix: a commit of the type fix patches a bug in your codebase (this correlates with PATCH in Semantic Versioning).
  2. feat: a commit of the type feat introduces a new feature to the codebase (this correlates with MINOR in Semantic Versioning).
  3. BREAKING CHANGE: a commit that has a footer BREAKING CHANGE:, or appends a ! after the type/scope, introduces a breaking API change (correlating with MAJOR in Semantic Versioning). A BREAKING CHANGE can be part of commits of any type.
  4. types other than fix: and feat: are allowed, for example @commitlint/config-conventional (based on the the Angular convention) recommends build:chore:ci:docs:style:refactor:perf:test:, and others.
  5. footers other than BREAKING CHANGE: <description> may be provided and follow a convention similar to git trailer format.

Additional types are not mandated by the Conventional Commits specification, and have no implicit effect in Semantic Versioning (unless they include a BREAKING CHANGE). A scope may be provided to a commit’s type, to provide additional contextual information and is contained within parenthesis, e.g., feat(parser): add ability to parse arrays.


GitLab Configuration

Our goal is, to automatically generate a file called on each commit to the main branch. In order to do that, we will use the tool semantic-release.

First, we will create a configuration file called .releaserc in the root folder:


With branches we define, which branches we’d like to generate a release. With the plugin section, we tell the tool, that commit messages should be analyzed with the conventionalcommits convention. Derived from the commit messages, it is determined, if the new release should be a Major, Minor, or just a Patch Release. The plugin release-notes-generator will generate the changelog-content. The Addon changelog is finally writing to the file. git creates a commit with the new file. With the parameter message, you can define the commit-message. Noticeable is the text [skip ci]. With that, you can tell GitLab, to skip triggering the CI-Pipeline – because we do not want to create a new version just for the new CHANGELOG file.

Additionally, we create the following file, to actually start the automatic create for commits to the main branch:


With only: main we define, that the section should just be executed, if the commit was in the main branch. In the section before_script we install the necessary software components. In script we finally execute the generation.

In order to successfully execute the tasks, an Access Token needs to be assigned (In order to allow the CI-Task to do commits). A personal access token can be generated, as described here. In the project settings CI / CD in the sections Variables you have to add the access token with the key GITLAB_TOKEN. This Variable should be marked as Protected and Masked.

Initially, we will commit both files to the main branch with the following commit-Message:

feat: Initial commit

Demo repository for an automated generation of and semantic versioning

The general workflow can be as following: During the normal development process, changes are pushed to development-Branches. After everything is ready, a Merge Request is created. After the Review it is merged to the main branch. In order to take over all changes, it is necessary to do not squash the commits.

On GitLab you can find a Demo-Repository with the described configurations: philipp.doblhofer/automatic-changelog-demo

Join the conversation


  1. Is node:17-buster-slim optional or required? What is its purpose in the script?

    1. You should be able to use any image which has npm and it’s requirements installed (or add the installation steps inside .gitlab-ci.yml for yourself). I choose the node image, because it already has npm installed.

  2. Hi Philipp,

    first of all thank you for the concise tutorial. I have set up everything as described and semantic-release works as expected. However, I had to disable the rules “Reject unverified users” and “Check whether the commit author is a GitLab user” in the push rules, because otherwise no new tag can be pushed (error reads “remote: GitLab: Author ‘… some bot email address i guess’ is not a member of team”). How did you solve this? Or am I missing something?

    Thanks a lot and best regards,

    1. Hello Lars,

      it seems, that the Access Token is not set correctly. Can you check if the CI/CD variable GITLAB_TOKEN was assigned? (Described in the paragraph in the blog post starting with ‘In order to successfully execute the task’)

      Best regards,

  3. Thank you very much for easy and clean explanation!
    It is really helpful.
    I used ‘node:slim’ image and also added semantic-release/gitlab plugin, to publish releases automatically in gitlab.

  4. Hi Philipp, I can’t get the script to work.
    I provided a private access token inside the CI_VARIABLES, but the script can’t pull the repository throwing the following error:

    [semantic-release] › ✘ An error occurred while running semantic-release: Error: Command failed with exit code 128: git ls-remote –heads
    error: cannot run ssh: No such file or directory
    fatal: unable to fork

    Do you have an idea, what the issue could be?

    1. Hello Felix,
      it seems that your CI Pipeline-Setup tries to use the ssh URL to the repository instead of the HTTPS URL. In the used image, no ssh binary is installed. You either can setup ssh with credentials in the pipeline script, or check why it is using the ssh URL (is your CI-Variable with the personal token called GITLAB_TOKEN? Can you access your gitlab instance with https via browser in general?)

      1. I got it working! The problem was that I had “protected” activated and my branch was not protected, so the variable was not accessible. Thanks for the fast help!

Leave a comment

Your email address will not be published. Required fields are marked *