Skip to main content

releases

GitLab Releases

Overview

GitLab Releases combine code, release notes, assets, and evidence into a complete release package. The Agent Platform automates release creation through CI/CD, generating professional releases with each version bump.

Release Components

A GitLab Release includes:

  1. Tag - Git tag marking the release (e.g., v0.3.5)
  2. Release Notes - Automatically generated from commits
  3. Assets - Binaries, packages, documentation
  4. Evidence - Audit trail (GitLab Ultimate)
  5. Milestones - Linked issues and MRs

Example Release

Tag:     v0.3.5
Date:    2026-01-08
Name:    Release v0.3.5
Branch:  release/v0.3.x

Release Notes:
   Features
  - OAuth2 authentication support (#106)
  - Distributed tracing integration (#108)

   Bug Fixes
  - Memory leak in agent runtime (#107)
  - Authentication token validation (#109)

Assets:
  - agent-platform-v0.3.5-linux-x64.tar.gz
  - agent-platform-v0.3.5-darwin-arm64.tar.gz
  - agent-platform-v0.3.5-windows-x64.zip
  - checksums.txt

Evidence:
  - release-evidence-v0.3.5.json (collected 2026-01-08 12:34:56 UTC)

Automated Release Creation

CI/CD Pipeline

# .gitlab-ci.yml stages: - validate - test - build - version - release - publish variables: RELEASE_REGISTRY: registry.gitlab.com/blueflyio/agent-platform # Calculate next version calculate-version: stage: version image: alpine/git script: - | # Extract branch version (release/v0.3.x 0.3) BRANCH_VERSION=$(echo $CI_COMMIT_REF_NAME | sed 's/release\/v\([0-9]*\.[0-9]*\)\.x/\1/') # Get latest tag for this release branch LATEST_TAG=$(git tag -l "v${BRANCH_VERSION}.*" --sort=-v:refname | head -n 1) if [ -z "$LATEST_TAG" ]; then NEXT_VERSION="v${BRANCH_VERSION}.0" else PATCH=$(echo $LATEST_TAG | awk -F. '{print $NF}') NEXT_PATCH=$((PATCH + 1)) NEXT_VERSION="v${BRANCH_VERSION}.${NEXT_PATCH}" fi echo "Next version: $NEXT_VERSION" echo "NEXT_VERSION=$NEXT_VERSION" >> version.env artifacts: reports: dotenv: version.env only: - /^release\/v[0-9]+\.[0-9]+\.x$/ # Build release artifacts build-artifacts: stage: build image: node:20-alpine script: - npm ci - npm run build # Create distribution packages - tar -czf agent-platform-${NEXT_VERSION}-linux-x64.tar.gz dist/ - tar -czf agent-platform-${NEXT_VERSION}-darwin-arm64.tar.gz dist/ - zip -r agent-platform-${NEXT_VERSION}-windows-x64.zip dist/ # Generate checksums - sha256sum *.tar.gz *.zip > checksums.txt artifacts: paths: - "*.tar.gz" - "*.zip" - "checksums.txt" expire_in: 1 year dependencies: - calculate-version only: - /^release\/v[0-9]+\.[0-9]+\.x$/ # Generate release notes generate-release-notes: stage: release image: node:20-alpine before_script: - npm install -g semantic-release @semantic-release/changelog @semantic-release/gitlab script: - | # Generate release notes using semantic-release npx semantic-release --dry-run > /tmp/release-output.txt # Extract release notes sed -n '/Published release/,/Release note/p' /tmp/release-output.txt > release-notes.md # Or generate manually from commits git log $(git describe --tags --abbrev=0 2>/dev/null || echo "")..HEAD \ --pretty=format:"- %s (%h)" \ --no-merges \ > release-notes-manual.md artifacts: paths: - release-notes.md - release-notes-manual.md dependencies: - calculate-version only: - /^release\/v[0-9]+\.[0-9]+\.x$/ # Create GitLab release create-gitlab-release: stage: release image: alpine:latest before_script: - apk add --no-cache git curl jq script: - | # Create git tag git config user.email "ci@agent-platform.io" git config user.name "GitLab CI" git tag -a $NEXT_VERSION -m "Release $NEXT_VERSION" git push origin $NEXT_VERSION # Prepare release notes RELEASE_NOTES=$(cat release-notes.md) # Upload release assets and create release using glab apk add --no-cache wget wget https://gitlab.com/gitlab-org/cli/-/releases/permalink/latest/downloads/glab_Linux_x86_64.tar.gz tar -xzf glab_Linux_x86_64.tar.gz mv bin/glab /usr/local/bin/ # Create release with assets glab release create $NEXT_VERSION \ --name "Release $NEXT_VERSION" \ --notes "$RELEASE_NOTES" \ --asset-link "{\"name\":\"Linux x64\",\"url\":\"${CI_PROJECT_URL}/-/jobs/${CI_JOB_ID}/artifacts/file/agent-platform-${NEXT_VERSION}-linux-x64.tar.gz\"}" \ --asset-link "{\"name\":\"macOS ARM64\",\"url\":\"${CI_PROJECT_URL}/-/jobs/${CI_JOB_ID}/artifacts/file/agent-platform-${NEXT_VERSION}-darwin-arm64.tar.gz\"}" \ --asset-link "{\"name\":\"Windows x64\",\"url\":\"${CI_PROJECT_URL}/-/jobs/${CI_JOB_ID}/artifacts/file/agent-platform-${NEXT_VERSION}-windows-x64.zip\"}" \ --asset-link "{\"name\":\"Checksums\",\"url\":\"${CI_PROJECT_URL}/-/jobs/${CI_JOB_ID}/artifacts/file/checksums.txt\"}" \ --repo $CI_PROJECT_PATH dependencies: - calculate-version - build-artifacts - generate-release-notes only: - /^release\/v[0-9]+\.[0-9]+\.x$/ when: manual

Using GitLab Releases API

Create Release

# Create release using API curl --request POST \ --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \ --header "Content-Type: application/json" \ --data '{ "tag_name": "v0.3.5", "name": "Release v0.3.5", "description": "## Features\n- OAuth2 authentication\n\n## Bug Fixes\n- Memory leak fix", "assets": { "links": [ { "name": "Linux x64", "url": "https://example.com/agent-platform-v0.3.5-linux-x64.tar.gz", "link_type": "package" }, { "name": "Documentation", "url": "https://docs.agent-platform.io/v0.3.5", "link_type": "other" } ] }, "milestones": ["v0.3.x"] }' \ "https://gitlab.com/api/v4/projects/$PROJECT_ID/releases"

Update Release

# Update existing release curl --request PUT \ --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \ --header "Content-Type: application/json" \ --data '{ "description": "Updated release notes..." }' \ "https://gitlab.com/api/v4/projects/$PROJECT_ID/releases/v0.3.5"

Get Release

# Get release information curl --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \ "https://gitlab.com/api/v4/projects/$PROJECT_ID/releases/v0.3.5"

List Releases

# List all releases curl --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \ "https://gitlab.com/api/v4/projects/$PROJECT_ID/releases"

Using glab CLI

# Create release glab release create v0.3.5 \ --name "Release v0.3.5" \ --notes "$(cat release-notes.md)" \ --repo blueflyio/openstandardagents # Create release with assets glab release create v0.3.5 \ --name "Release v0.3.5" \ --notes "Release notes here" \ --asset-link '{"name":"Binary","url":"https://example.com/binary.tar.gz"}' \ --repo blueflyio/openstandardagents # Update release glab release update v0.3.5 \ --notes "Updated notes" \ --repo blueflyio/openstandardagents # View release glab release view v0.3.5 \ --repo blueflyio/openstandardagents # List releases glab release list \ --repo blueflyio/openstandardagents # Download release assets glab release download v0.3.5 \ --repo blueflyio/openstandardagents

Release Notes Generation

Automatic from Commits

#!/bin/bash # generate-release-notes.sh PREVIOUS_TAG=$(git describe --tags --abbrev=0 HEAD^ 2>/dev/null || echo "") CURRENT_TAG=$1 if [ -z "$PREVIOUS_TAG" ]; then COMMIT_RANGE="HEAD" else COMMIT_RANGE="${PREVIOUS_TAG}..HEAD" fi echo "# Release ${CURRENT_TAG}" echo "" echo "Release date: $(date +%Y-%m-%d)" echo "" # Features FEATURES=$(git log $COMMIT_RANGE --pretty=format:"- %s (%h)" --no-merges | grep "^- feat:") if [ ! -z "$FEATURES" ]; then echo "## Features" echo "" echo "$FEATURES" | sed 's/^- feat: /- /' echo "" fi # Bug Fixes FIXES=$(git log $COMMIT_RANGE --pretty=format:"- %s (%h)" --no-merges | grep "^- fix:") if [ ! -z "$FIXES" ]; then echo "## Bug Fixes" echo "" echo "$FIXES" | sed 's/^- fix: /- /' echo "" fi # Breaking Changes BREAKING=$(git log $COMMIT_RANGE --pretty=format:"%B" --no-merges | grep -i "BREAKING CHANGE") if [ ! -z "$BREAKING" ]; then echo "## Breaking Changes" echo "" echo "$BREAKING" echo "" fi # Performance PERF=$(git log $COMMIT_RANGE --pretty=format:"- %s (%h)" --no-merges | grep "^- perf:") if [ ! -z "$PERF" ]; then echo "## Performance Improvements" echo "" echo "$PERF" | sed 's/^- perf: /- /' echo "" fi # Documentation DOCS=$(git log $COMMIT_RANGE --pretty=format:"- %s (%h)" --no-merges | grep "^- docs:") if [ ! -z "$DOCS" ]; then echo "## Documentation" echo "" echo "$DOCS" | sed 's/^- docs: /- /' echo "" fi # Contributors echo "## Contributors" echo "" git log $COMMIT_RANGE --format="%aN" --no-merges | sort -u | sed 's/^/- @/' echo "" # Stats echo "## Statistics" echo "" COMMITS=$(git log $COMMIT_RANGE --oneline --no-merges | wc -l) FILES=$(git diff $PREVIOUS_TAG..HEAD --name-only | wc -l) ADDITIONS=$(git diff $PREVIOUS_TAG..HEAD --numstat | awk '{add+=$1} END {print add}') DELETIONS=$(git diff $PREVIOUS_TAG..HEAD --numstat | awk '{del+=$2} END {print del}') echo "- $COMMITS commits" echo "- $FILES files changed" echo "- $ADDITIONS additions" echo "- $DELETIONS deletions"

Using semantic-release

// release-notes-generator.js const conventionalChangelogCore = require('conventional-changelog-core'); const fs = require('fs'); const config = { preset: 'conventionalcommits', releaseCount: 1 }; const stream = conventionalChangelogCore(config) .on('error', err => { console.error('Error generating release notes:', err); process.exit(1); }); let releaseNotes = ''; stream.on('data', chunk => { releaseNotes += chunk.toString(); }); stream.on('end', () => { fs.writeFileSync('release-notes.md', releaseNotes); console.log('Release notes generated: release-notes.md'); });

Custom Template

# Release {{version}} **Release Date**: {{date}} **Release Branch**: {{branch}} **Milestone**: {{milestone}} ## What's New {{#if features}} ### Features {{#each features}} - {{subject}} ({{shortHash}}) {{#if issues}}[#{{issues}}]{{/if}} {{/each}} {{/if}} {{#if fixes}} ### Bug Fixes {{#each fixes}} - {{subject}} ({{shortHash}}) {{#if issues}}[#{{issues}}]{{/if}} {{/each}} {{/if}} {{#if breaking}} ### Breaking Changes {{#each breaking}} - {{subject}} {{body}} {{/each}} {{/if}} ## Installation ### NPM \`\`\`bash npm install @agent-platform/core@{{version}} \`\`\` ### Docker \`\`\`bash docker pull registry.gitlab.com/blueflyio/agent-platform/core:{{version}} \`\`\` ## Downloads - [Linux x64]({{linux_url}}) - [macOS ARM64]({{macos_url}}) - [Windows x64]({{windows_url}}) - [Checksums]({{checksums_url}}) ## Documentation - [Release Documentation](https://docs.agent-platform.io/{{version}}) - [Migration Guide](https://docs.agent-platform.io/{{version}}/migration) - [Changelog]({{changelog_url}}) ## Contributors {{#each contributors}} - @{{name}} {{/each}} --- **Full Changelog**: [{{previousVersion}}...{{version}}]({{compareUrl}})

Release Assets

Types of Assets

Asset TypePurposeExample
PackagesBinary distributionsagent-platform-v0.3.5-linux-x64.tar.gz
SourceSource code archivesGitLab auto-generates
DocumentationOffline docsdocs-v0.3.5.pdf
ChecksumsIntegrity verificationchecksums.txt
SignaturesSecurity verificationagent-platform-v0.3.5.sig
Container ImagesDocker imagesLink to registry

Building Release Assets

# .gitlab-ci.yml build-release-assets: stage: build image: node:20-alpine script: # Build binaries - npm ci - npm run build # Package for different platforms - | # Linux x64 cd dist tar -czf ../agent-platform-${NEXT_VERSION}-linux-x64.tar.gz . cd .. # macOS ARM64 cd dist tar -czf ../agent-platform-${NEXT_VERSION}-darwin-arm64.tar.gz . cd .. # Windows x64 cd dist zip -r ../agent-platform-${NEXT_VERSION}-windows-x64.zip . cd .. # Generate checksums - sha256sum *.tar.gz *.zip > checksums.txt - cat checksums.txt # Sign releases (optional) - | if [ ! -z "$GPG_PRIVATE_KEY" ]; then echo "$GPG_PRIVATE_KEY" | gpg --import for file in *.tar.gz *.zip; do gpg --detach-sign --armor $file done fi # Generate SBOM (Software Bill of Materials) - npm sbom --output-file sbom.json artifacts: paths: - "*.tar.gz" - "*.zip" - "*.asc" - "checksums.txt" - "sbom.json" expire_in: 1 year dependencies: - calculate-version only: - /^release\/v[0-9]+\.[0-9]+\.x$/

Uploading Assets

# Upload release assets using API upload_asset() { local file=$1 local name=$2 local url=$3 # Upload to artifact storage (S3, GitLab, etc.) ASSET_URL=$(upload-to-storage "$file") # Add asset link to release curl --request POST \ --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \ --header "Content-Type: application/json" \ --data "{ \"name\": \"$name\", \"url\": \"$ASSET_URL\", \"link_type\": \"package\" }" \ "https://gitlab.com/api/v4/projects/$PROJECT_ID/releases/$TAG/assets/links" } # Upload all assets upload_asset "agent-platform-v0.3.5-linux-x64.tar.gz" "Linux x64" upload_asset "agent-platform-v0.3.5-darwin-arm64.tar.gz" "macOS ARM64" upload_asset "agent-platform-v0.3.5-windows-x64.zip" "Windows x64" upload_asset "checksums.txt" "Checksums"

Release Evidence

GitLab Ultimate automatically collects release evidence for compliance and audit purposes.

Evidence Collection

Evidence is automatically collected when a release is created and includes:

  • Release metadata (tag, name, created_at)
  • Release assets and links
  • Linked milestones with issues and MRs
  • Pipeline information
  • Project information

Example evidence (release-evidence-v0.3.5.json):

{ "release": { "tag_name": "v0.3.5", "name": "Release v0.3.5", "project_name": "openstandardagents", "created_at": "2026-01-08T12:34:56Z" }, "milestones": [ { "id": 123, "title": "v0.3.x", "state": "active", "issues": [ { "id": 106, "title": "Add OAuth2 authentication", "state": "closed" } ] } ], "pipeline": { "id": 456, "status": "success", "ref": "release/v0.3.x" } }

Manual Evidence Collection

# Trigger evidence collection manually curl --request POST \ --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \ "https://gitlab.com/api/v4/projects/$PROJECT_ID/releases/v0.3.5/evidence"

Accessing Evidence

# Get release evidence curl --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \ "https://gitlab.com/api/v4/projects/$PROJECT_ID/releases/v0.3.5" | \ jq '.evidences' # Download evidence JSON curl --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \ "https://gitlab.com/api/v4/projects/$PROJECT_ID/releases/v0.3.5/evidences/1" \ -o release-evidence-v0.3.5.json

Release Workflow

Complete Release Process

1. Development
    Feature branches  release/v0.3.x
    MRs merged
    CI runs tests

2. Version Calculation
    Extract branch version
    Get latest tag
    Calculate next patch

3. Build Artifacts
    Compile binaries
    Package distributions
    Generate checksums
    Sign packages

4. Generate Release Notes
    Analyze commits
    Group by type
    Link issues
    Format markdown

5. Create Git Tag
    Tag commit
    Push to remote

6. Create GitLab Release
    Upload assets
    Attach release notes
    Link milestones
    Collect evidence

7. Publish
    Push to package registry
    Update documentation
    Notify stakeholders
    Close milestone (if complete)

Manual Release Steps

For manual releases or testing:

# 1. Calculate version BRANCH_VERSION=$(git rev-parse --abbrev-ref HEAD | sed 's/release\/v\([0-9]*\.[0-9]*\)\.x/\1/') LATEST_TAG=$(git tag -l "v${BRANCH_VERSION}.*" --sort=-v:refname | head -n 1) PATCH=$(echo $LATEST_TAG | awk -F. '{print $NF}') NEXT_VERSION="v${BRANCH_VERSION}.$((PATCH + 1))" echo "Next version: $NEXT_VERSION" # 2. Build artifacts npm ci npm run build tar -czf agent-platform-${NEXT_VERSION}-linux-x64.tar.gz dist/ sha256sum *.tar.gz > checksums.txt # 3. Generate release notes ./scripts/generate-release-notes.sh $NEXT_VERSION > release-notes.md # 4. Create tag git tag -a $NEXT_VERSION -m "Release $NEXT_VERSION" git push origin $NEXT_VERSION # 5. Create GitLab release glab release create $NEXT_VERSION \ --name "Release $NEXT_VERSION" \ --notes "$(cat release-notes.md)" \ --asset-link "{\"name\":\"Linux x64\",\"url\":\"$(upload-file agent-platform-${NEXT_VERSION}-linux-x64.tar.gz)\"}"

Release Strategies

Continuous Releases

Every merge to release branch creates a new release:

MR #1 merges  v0.3.1
MR #2 merges  v0.3.2
MR #3 merges  v0.3.3

Pros: Fast feedback, frequent releases Cons: Many versions, potential noise

Batched Releases

Accumulate changes, release periodically:

Week 1: MR #1, #2, #3 merge
Week 2: Create v0.3.1 release

Pros: Fewer releases, time to test Cons: Slower feedback, larger releases

Milestone-Based Releases

Release when milestone is complete:

Milestone v0.3.x:
  - Issue #106 
  - Issue #107 
  - Issue #108 
 Release v0.3.1

Pros: Feature-complete releases Cons: Unpredictable timing

Agent Platform Strategy

The Agent Platform uses continuous releases with manual trigger:

create-gitlab-release: stage: release script: - # Create release when: manual # Manual approval required

Benefits:

  • Changes tested and ready immediately
  • Manual control over release timing
  • Option to batch multiple MRs before releasing

Best Practices

DO

  1. Automate everything

    • Version calculation
    • Release notes generation
    • Asset building
    • Release creation
  2. Include comprehensive release notes

    • What changed
    • Why it changed
    • Migration instructions
    • Breaking changes
  3. Provide multiple formats

    • Binaries for all platforms
    • Container images
    • Source archives
    • Documentation
  4. Verify assets

    • Generate checksums
    • Sign releases
    • Test downloads
  5. Link issues and MRs

    • Associate with milestones
    • Reference in release notes
    • Close related issues
  6. Collect evidence

    • Enable automatic evidence
    • Store for compliance
    • Audit release process

DON'T

  1. Don't manually create releases

    • Use CI/CD automation
    • Avoid human error
  2. Don't skip release notes

    • Always document changes
    • Make notes meaningful
  3. Don't forget assets

    • Include all platforms
    • Provide checksums
    • Test downloads
  4. Don't delete releases

    • Keep historical releases
    • Maintain download links
  5. Don't rush releases

    • Test thoroughly
    • Review release notes
    • Verify assets

Troubleshooting

Issue: Release Creation Fails

Problem: GitLab API returns 400 error

Solution:

# Verify tag exists git tag -l "v0.3.5" # Check release doesn't already exist glab release view v0.3.5 # Validate JSON payload echo "$RELEASE_DATA" | jq .

Issue: Assets Not Appearing

Problem: Release created but assets missing

Solution:

# Check asset links glab release view v0.3.5 --web # Verify URLs are accessible curl -I "https://example.com/asset.tar.gz" # Re-upload assets glab release create v0.3.5 \ --asset-link '{"name":"Binary","url":"https://example.com/binary.tar.gz"}'

Issue: Evidence Not Collected

Problem: Release evidence not generated

Solution:

# Trigger evidence collection manually curl --request POST \ --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \ "https://gitlab.com/api/v4/projects/$PROJECT_ID/releases/v0.3.5/evidence" # Verify evidence exists glab api "/projects/$PROJECT_ID/releases/v0.3.5/evidences"

Resources

See Also