Skip to content
DevOps AI ToolKit
Newsletter
All guides
AI for GitLab CI/CD By James Joyner IV · · 9 min read

Automating Releases With GitLab CI: Semantic Versioning and Changelogs

Manual releases are slow and error-prone. Here's how I automate versioning, changelogs, tags, and release notes in GitLab CI so shipping a release is a single merge.

  • #gitlab
  • #cicd
  • #releases
  • #semantic-versioning
  • #changelog
  • #automation

For years, cutting a release at most companies looked like this: someone remembers it’s release day, manually bumps a version number, hand-writes a changelog from memory, creates a tag, and forgets one of those steps half the time. The result is releases that are infrequent, inconsistent, and occasionally wrong about what’s even in them.

Releases should be boring and automatic. After building release pipelines across many teams, here’s how I make a release nothing more than a side effect of merging — with correct versions, real changelogs, and a proper GitLab release every time.

The foundation: conventional commits

Automated versioning needs a machine-readable signal for “what changed.” Conventional Commits provide it. Every commit message starts with a type:

  • fix: — a bug fix, bumps the patch version.
  • feat: — a new feature, bumps the minor version.
  • feat!: or a BREAKING CHANGE: footer — bumps the major version.
feat(auth): add OIDC login support
fix(api): handle null user in profile endpoint

Once commits carry this structure, a tool can compute the next version and generate the changelog automatically. No human decides “is this 1.4.0 or 1.5.0” — the commits decide.

Enforce the format with a commit-lint job so a non-conforming message can’t sneak in:

commitlint:
  stage: test
  rules:
    - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
  script:
    - npx commitlint --from $CI_MERGE_REQUEST_DIFF_BASE_SHA --to HEAD

Compute the version automatically

With conventional commits in place, a release job determines the next version from the commit history since the last tag. Tools like semantic-release or release-please do this well, but even GitLab’s built-in release features go a long way:

prepare-release:
  stage: release
  rules:
    - if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
  script:
    - npx semantic-release

The job reads the commits, decides the version, and never gets it wrong because of someone’s late-night arithmetic.

Generate the changelog from the source of truth

GitLab can build a changelog directly from commits via its API and the changelog command:

generate-changelog:
  stage: release
  rules:
    - if: '$CI_COMMIT_TAG'
  script:
    - >
      curl --request POST --header "PRIVATE-TOKEN: $CHANGELOG_TOKEN"
      "$CI_API_V4_URL/projects/$CI_PROJECT_ID/repository/changelog?version=$CI_COMMIT_TAG"

The changelog is generated from what actually merged, not from what someone remembered to write down. That difference matters: a hand-written changelog always drifts from reality, and an auto-generated one can’t.

Create the GitLab release

The release keyword turns a tag into a first-class GitLab release with notes, assets, and a permanent record:

release:
  stage: release
  image: registry.gitlab.com/gitlab-org/release-cli:latest
  rules:
    - if: '$CI_COMMIT_TAG =~ /^v\d+\.\d+\.\d+$/'
  script:
    - echo "Creating release for $CI_COMMIT_TAG"
  release:
    tag_name: $CI_COMMIT_TAG
    name: "Release $CI_COMMIT_TAG"
    description: "./CHANGELOG.md"
    assets:
      links:
        - name: "Container image"
          url: "$CI_REGISTRY_IMAGE:$CI_COMMIT_TAG"

Now every release has a page, notes, and links to its artifacts — automatically.

Tie the release to the deploy

A version that isn’t deployed is just a number. Wire the release to a deploy job so the tag is what triggers the rollout:

deploy-prod:
  stage: deploy
  needs: ["release"]
  rules:
    - if: '$CI_COMMIT_TAG'
      when: manual
  environment:
    name: production
  script:
    - ./deploy.sh $CI_COMMIT_TAG

I keep production deploys manual even when everything else is automated, so a human still presses the button. The automation removes the toil; the human keeps the judgment.

Make rollbacks just as easy

Automated releases are only half the story — you also want effortless rollbacks. Because every release is a tagged, immutable artifact, rolling back is just deploying the previous tag. Keep your deploy script version-parameterized so a rollback is ./deploy.sh v1.3.4, not a panicked manual scramble. The easier the rollback, the more confidently the team ships.

Pre-releases and release candidates

For teams that want a staging gate, conventional-commit tooling supports pre-releases. Merges to a next branch produce 1.5.0-rc.1 tags that deploy to staging; promotion to main cuts the real 1.5.0. This gives you a clean train from “merged” to “release candidate” to “released” without any manual version juggling.

Where AI helps

The release notes are where automation can feel robotic — a raw list of commit subjects isn’t great communication. I feed the generated changelog into a model and ask it to write a human-readable summary grouped by theme, with a short “highlights” section, while keeping the full detailed list below. It turns a wall of fix: lines into notes a user actually reads. I also use it to sanity-check version bumps: “Given these commits, is this correctly a minor and not a major release?” I keep GitLab CI prompts for release notes, and run release-pipeline changes through our Code Review tool before merging.

A release should be a merge

The goal is simple: a release is what happens when you merge to main, not a ceremony someone performs. The version is computed, the changelog is generated, the tag is created, the release page is published, and the deploy is one button away — all from the commit history.

Get there and releasing stops being scary. Teams that can release effortlessly release often, in small safe increments, which is exactly the habit good CI/CD is supposed to build.

AI suggestions for versioning and release notes are assistive, not authoritative. Always verify the computed version and changelog against the actual changes before publishing a release.

Free download · 368-page PDF

Download the Free 500-Prompt DevOps AI Toolkit

500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.

  • 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
  • Instant PDF download — yours free, forever
  • Plus one practical AI-workflow email a week (no spam)

Single opt-in · unsubscribe anytime · no spam.