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:
- Tag - Git tag marking the release (e.g.,
v0.3.5) - Release Notes - Automatically generated from commits
- Assets - Binaries, packages, documentation
- Evidence - Audit trail (GitLab Ultimate)
- 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 Type | Purpose | Example |
|---|---|---|
| Packages | Binary distributions | agent-platform-v0.3.5-linux-x64.tar.gz |
| Source | Source code archives | GitLab auto-generates |
| Documentation | Offline docs | docs-v0.3.5.pdf |
| Checksums | Integrity verification | checksums.txt |
| Signatures | Security verification | agent-platform-v0.3.5.sig |
| Container Images | Docker images | Link 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
-
Automate everything
- Version calculation
- Release notes generation
- Asset building
- Release creation
-
Include comprehensive release notes
- What changed
- Why it changed
- Migration instructions
- Breaking changes
-
Provide multiple formats
- Binaries for all platforms
- Container images
- Source archives
- Documentation
-
Verify assets
- Generate checksums
- Sign releases
- Test downloads
-
Link issues and MRs
- Associate with milestones
- Reference in release notes
- Close related issues
-
Collect evidence
- Enable automatic evidence
- Store for compliance
- Audit release process
DON'T
-
Don't manually create releases
- Use CI/CD automation
- Avoid human error
-
Don't skip release notes
- Always document changes
- Make notes meaningful
-
Don't forget assets
- Include all platforms
- Provide checksums
- Test downloads
-
Don't delete releases
- Keep historical releases
- Maintain download links
-
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"