GitLab CI can automatically build and publish releases from Git tags, transforming your tagging workflow into a streamlined release process.

Let’s see this in action. Imagine you’ve just tagged a new version of your software: v1.2.0. Your .gitlab-ci.yml file can be configured to detect this tag and trigger a specific job.

stages:
  - build
  - release

variables:
  APP_VERSION: ${CI_COMMIT_TAG#v} # Removes 'v' prefix if present

build_app:
  stage: build
  script:
    - echo "Building application version ${APP_VERSION}..."
    - # Your build commands here (e.g., docker build, make, npm install)
    - echo "Build complete."
  only:
    - tags # This job runs only for tags

publish_release:
  stage: release
  script:
    - echo "Publishing release for tag ${CI_COMMIT_TAG}..."
    - echo "Version: ${APP_VERSION}"
    - # Commands to publish your release (e.g., push to Docker Hub, upload to S3, create GitHub release)
    - echo "Release published successfully."
  only:
    - tags # This job runs only for tags
  when: on_success # Only run if the 'build_app' job succeeds

In this example:

  • The build_app job runs as part of the build stage. It uses only: - tags to ensure it only executes when a Git tag is pushed. The APP_VERSION variable is defined to automatically extract the version number from the tag, stripping a leading v if it exists (e.g., v1.2.0 becomes 1.2.0).
  • The publish_release job, in the release stage, also runs only: - tags. The when: on_success clause ensures it only runs if the preceding build_app job completed without errors. This job contains the logic for actually publishing your release artifacts.

The core problem this solves is manual release management. Without automation, you’d be pushing code, manually creating tags, then manually building artifacts, uploading them, and updating release notes. GitLab CI automates this entire sequence, reducing human error and saving significant time.

Internally, GitLab CI uses predefined CI/CD variables to provide context about the pipeline. CI_COMMIT_TAG is crucial here; it holds the name of the tag that triggered the pipeline. When you push a tag, GitLab recognizes it as a special type of commit and can be instructed to run specific jobs based on it. The only: - tags directive tells the CI system to filter jobs and only execute them when the pipeline is triggered by a tag push.

The APP_VERSION: ${CI_COMMIT_TAG#v} syntax is a shell parameter expansion. It takes the value of CI_COMMIT_TAG and removes the shortest match of v from the beginning of the string. This is incredibly useful for standardizing versioning in your build artifacts, regardless of whether you tag as v1.0.0 or 1.0.0.

The when: on_success keyword is powerful for creating sequential release steps. It guarantees that your release publishing logic only executes if the preceding build steps have successfully completed. This prevents partial or broken releases from being published.

A common misconception is that you need complex scripting to handle different tag patterns. However, GitLab’s predefined variables and simple shell expansions are often sufficient. For instance, if you consistently tag with semantic versioning (e.g., vMAJOR.MINOR.PATCH), you can easily extract these components. If you needed to publish to a specific registry based on the tag, you could use if [[ "${CI_COMMIT_TAG}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]] within your script to conditionally execute publishing commands, targeting only valid version tags.

The next concept you’ll likely explore is using GitLab’s Release feature itself, where you can create official release objects directly within GitLab, often populated automatically by these CI jobs.

Want structured learning?

Take the full Gitlab-ci course →