GitLab’s release tracking is less about knowing when a version shipped and more about understanding why it shipped and what it actually contains.

Imagine you have a simple web app. You push code to a main branch. GitLab CI/CD kicks in.

stages:
  - build
  - deploy

build_app:
  stage: build
  script:
    - echo "Building application..."
    - echo "APP_VERSION=$(git rev-parse --short HEAD)" > build.env # Use short commit hash as version
    - echo "Built successfully!"
  artifacts:
    reports:
      dotenv: build.env

deploy_production:
  stage: deploy
  script:
    - echo "Deploying version $APP_VERSION to production..."
    - echo "Simulating deployment..."
    - echo "Deployment complete for $APP_VERSION."
  environment:
    name: production
    url: https://my-app.example.com
  when: manual # Manual trigger for production
  needs:
    - job: build_app
      artifacts: true

This pipeline builds the app, tags the artifact with the Git commit hash, and then has a manual job to deploy it to a production environment. The environment block is key here. It tells GitLab that this job is responsible for a specific deployment to a named environment, and crucially, it associates the deployment with the Git commit that was built.

When you click "play" on deploy_production, GitLab records this as a deployment. You can see this in the project’s Deployments section. This isn’t just a log; it’s a structured record. Each deployment entry shows:

  • The environment (production).
  • The commit SHA it was deployed from.
  • The user who triggered it (or if it was automatic).
  • The date and time.
  • The status (success, failed, etc.).

This allows you to go to a specific commit in your Git history and see exactly which environments it was deployed to. Conversely, you can go to an environment (like production) and see the latest commit that was deployed there. This linkage is the core of GitLab’s release tracking.

What if you want to deploy to multiple environments sequentially? You can define them in your .gitlab-ci.yml:

stages:
  - deploy

deploy_staging:
  stage: deploy
  script:
    - echo "Deploying to staging..."
    - echo "Simulating staging deployment."
  environment:
    name: staging
    url: https://staging.my-app.example.com

deploy_production:
  stage: deploy
  script:
    - echo "Deploying to production..."
    - echo "Simulating production deployment."
  environment:
    name: production
    url: https://my-app.example.com
  when: manual
  needs:
    - deploy_staging # This ensures staging deploys first

Here, deploy_production depends on deploy_staging. If deploy_staging completes successfully, you can then manually trigger deploy_production. GitLab visualizes this flow in the Environments page, showing you the status of each environment and what’s currently deployed to it.

The real power comes when you combine this with GitLab Releases. You can create a formal "Release" in GitLab, which is essentially a tag in your Git repository. This tag can then be linked to a specific deployment.

release_app:
  stage: release
  script:
    - echo "Creating Git tag for release..."
    - git tag v1.0.0 HEAD # Tag the current commit
    - git push origin v1.0.0 # Push the tag
    - echo "Release v1.0.0 created and deployed to environments."
  release:
    tag_name: v1.0.0
    description: "Release v1.0.0 - New feature X and bug fixes."
  only:
    - main # Only run on main branch

When this job runs, it creates a Git tag (v1.0.0 in this example) and simultaneously creates a GitLab Release associated with that tag. This Release can aggregate information about the commit, the associated deployments, and any release notes you provide. You can then navigate to Deployments > Releases and see your formally tagged releases, and for each release, see where it was deployed.

The most surprising thing about GitLab’s release tracking is how it leverages Git’s fundamental concepts (commits, tags) and extends them with structured metadata, turning a simple commit history into a verifiable audit trail of what code made it to which production system, and when. It’s not just about CI/CD pipelines; it’s about the traceability of every change.

If you have a job that uses the environment keyword, and it fails, GitLab will mark that environment as "stopped" or "unavailable" until a new job successfully deploys to it. This is because GitLab assumes a successful environment job means that environment is now running the code from that specific commit.

The next logical step is to integrate this with security scanning, so you can see the security posture of each deployed version.

Want structured learning?

Take the full Gitlab course →