Last updated: Sep 11, 2025, 03:42 PM UTC

How to Implement and Manage Software Versioning

Purpose: A comprehensive guide for implementing semantic versioning in software projects
Audience: Developers, DevOps engineers, and project maintainers
Updated: 2025-08-12 UTC


Why Versioning Matters

Proper versioning is crucial for:

  • Tracking Changes: Know exactly what's in each release
  • Communication: Clear communication about breaking changes
  • Rollback Capability: Quickly revert to previous versions
  • Dependency Management: Other projects can depend on specific versions
  • Professional Practice: Industry standard for software releases

Overview

This guide shows you how to implement a professional semantic versioning system for any software project, using Sasha Studio as a practical example. You'll learn how to:

  • Set up semantic versioning
  • Automate version management
  • Integrate with Docker builds
  • Create a CI/CD versioning pipeline
  • Handle releases properly

Understanding Semantic Versioning

The Standard Format

Semantic Versioning (SemVer) follows Semantic Versioning 2.0.0 specification:

MAJOR.MINOR.PATCH[-PRERELEASE][+BUILD]

Version Components Explained

Core Version Numbers

  • MAJOR (X.0.0): Breaking changes that are incompatible with previous versions

    • API changes that break existing integrations
    • Removal of features
    • Major architectural changes
  • MINOR (0.X.0): New features added in a backward-compatible manner

    • New functionality
    • New API endpoints
    • Deprecation notices (but not removal)
  • PATCH (0.0.X): Backward-compatible bug fixes

    • Security patches
    • Performance improvements
    • Documentation updates

Optional Components

  • PRERELEASE: Optional pre-release identifier (alpha, beta, rc)

    • Used for testing versions before official release
    • Example: 1.0.0-beta.1
  • BUILD: Optional build metadata (date, commit hash)

    • For tracking specific builds
    • Example: 1.0.0+20240111.abc123

Real-World Examples

1.0.0          # First stable release
1.1.0          # Added new feature (backward compatible)
1.0.1          # Bug fix to 1.0.0
2.0.0          # Breaking change (not backward compatible)
2.0.0-beta.1   # Beta version for testing
2.0.0-rc.1     # Release candidate
1.0.0+20240111 # Build with metadata

When to Increment Each Number

Change Type Version Change Example
Fix typo in docs 1.0.0 β†’ 1.0.1 PATCH
Add new API endpoint 1.0.1 β†’ 1.1.0 MINOR
Remove deprecated API 1.1.0 β†’ 2.0.0 MAJOR
Security fix 1.1.0 β†’ 1.1.1 PATCH
Performance improvement 1.1.1 β†’ 1.1.2 PATCH
Add optional parameter 1.1.2 β†’ 1.2.0 MINOR
Change required parameter 1.2.0 β†’ 2.0.0 MAJOR

Setting Up Version Management

Step 1: Create a Central Version File

Create a single VERSION file at your project root:

# Create VERSION file
echo "1.0.0" > VERSION

# Verify it was created
cat VERSION
# Output: 1.0.0

Important: This file should contain ONLY the version number (no extra text or whitespace) and serves as the single source of truth for all versioning operations.

Step 2: Update package.json (for Node.js projects)

{
  "name": "your-project",
  "version": "1.0.0",  // Keep in sync with VERSION file
  "scripts": {
    "version:sync": "node scripts/sync-version.js"
  }
}

Step 3: Set Up Automatic Version Bumping

Option A: Auto-bump on Build

Create a build script that automatically increments the patch version:

#!/bin/bash
# build.sh - Auto-bumps patch version on each build

# Read current version
CURRENT_VERSION=$(cat VERSION)

# Bump patch version (unless --no-bump flag is used)
if [[ "$1" != "--no-bump" ]]; then
    # Split version into components
    IFS='.' read -r major minor patch <<< "$CURRENT_VERSION"
    patch=$((patch + 1))
    NEW_VERSION="${major}.${minor}.${patch}"
    
    # Update VERSION file
    echo "$NEW_VERSION" > VERSION
    echo "Version bumped: $CURRENT_VERSION β†’ $NEW_VERSION"
fi

# Continue with build...

Option B: Manual Version Control

For more control, use manual version bumping:

./scripts/version.sh patch    # 1.0.0 β†’ 1.0.1
./scripts/version.sh minor    # 1.0.1 β†’ 1.1.0
./scripts/version.sh major    # 1.1.0 β†’ 2.0.0

Creating a Version Management Script

Complete Version Script Example

Create scripts/version.sh to manage versions:

#!/bin/bash
# scripts/version.sh - Semantic version management

VERSION_FILE="VERSION"

# Read current version
CURRENT_VERSION=$(cat $VERSION_FILE 2>/dev/null || echo "0.0.0")

case "$1" in
    current)
        echo "Current version: $CURRENT_VERSION"
        ;;
    
    major)
        # Bump major version (X.0.0)
        IFS='.' read -r major minor patch <<< "$CURRENT_VERSION"
        NEW_VERSION="$((major + 1)).0.0"
        echo "$NEW_VERSION" > $VERSION_FILE
        echo "Version bumped: $CURRENT_VERSION β†’ $NEW_VERSION (MAJOR)"
        ;;
    
    minor)
        # Bump minor version (0.X.0)
        IFS='.' read -r major minor patch <<< "$CURRENT_VERSION"
        NEW_VERSION="${major}.$((minor + 1)).0"
        echo "$NEW_VERSION" > $VERSION_FILE
        echo "Version bumped: $CURRENT_VERSION β†’ $NEW_VERSION (MINOR)"
        ;;
    
    patch)
        # Bump patch version (0.0.X)
        IFS='.' read -r major minor patch <<< "$CURRENT_VERSION"
        NEW_VERSION="${major}.${minor}.$((patch + 1))"
        echo "$NEW_VERSION" > $VERSION_FILE
        echo "Version bumped: $CURRENT_VERSION β†’ $NEW_VERSION (PATCH)"
        ;;
    
    prerelease)
        # Create pre-release version
        TYPE=${2:-beta}
        if [[ $CURRENT_VERSION == *"-${TYPE}."* ]]; then
            # Increment existing pre-release
            PRERELEASE_NUM=$(echo $CURRENT_VERSION | sed -n "s/.*-${TYPE}.\([0-9]*\).*/\1/p")
            NEW_VERSION=$(echo $CURRENT_VERSION | sed "s/-${TYPE}.[0-9]*/-${TYPE}.$((PRERELEASE_NUM + 1))/")
        else
            # Create new pre-release
            BASE_VERSION=$(echo $CURRENT_VERSION | cut -d'-' -f1)
            NEW_VERSION="${BASE_VERSION}-${TYPE}.1"
        fi
        echo "$NEW_VERSION" > $VERSION_FILE
        echo "Pre-release version: $CURRENT_VERSION β†’ $NEW_VERSION"
        ;;
    
    set)
        # Set specific version
        NEW_VERSION="$2"
        echo "$NEW_VERSION" > $VERSION_FILE
        echo "Version set to: $NEW_VERSION"
        ;;
    
    *)
        echo "Usage: $0 {current|major|minor|patch|prerelease [type]|set <version>}"
        echo ""
        echo "Examples:"
        echo "  $0 current           # Show current version"
        echo "  $0 patch            # Bump patch: 1.0.0 β†’ 1.0.1"
        echo "  $0 minor            # Bump minor: 1.0.1 β†’ 1.1.0"
        echo "  $0 major            # Bump major: 1.1.0 β†’ 2.0.0"
        echo "  $0 prerelease beta  # Create beta: 1.0.0 β†’ 1.0.0-beta.1"
        echo "  $0 set 2.0.0        # Set specific version"
        exit 1
        ;;
esac

Make the script executable:

chmod +x scripts/version.sh

Integrating Versioning with Docker

Docker Build Integration

The enhanced claudecodeui/scripts/docker-build.sh script provides comprehensive versioning support and now uses Dockerfile.sliplane by default (optimized for Sliplane deployment):

# Production build with automatic versioning
./claudecodeui/scripts/docker-build.sh production

# Development build
./claudecodeui/scripts/docker-build.sh development

# Custom tagged build
./claudecodeui/scripts/docker-build.sh production my-custom-tag

Docker Tags Created

For a production build of version 1.2.3, the following tags are created:

  • sasha-studio:1.2.3 - Full version tag
  • sasha-studio:1.2 - Major.minor tag
  • sasha-studio:1 - Major version tag
  • sasha-studio:latest - Latest stable
  • sasha-studio:production - Production tag

Additional tags for development:

  • sasha-studio:dev - Development build
  • sasha-studio:dev-abc123 - Dev with commit hash
  • sasha-studio:feature-branch - Branch-based tag
  • sasha-studio:1.2.3-dev.20240111.abc123 - Dev with full metadata

Build Metadata

Each Docker image includes metadata labels:

  • org.opencontainers.image.version - Version number
  • org.opencontainers.image.created - Build timestamp
  • org.opencontainers.image.revision - Git commit hash
  • org.opencontainers.image.title - Application name
  • org.opencontainers.image.description - Application description

Testing with Versions

The docker-test.sh script automatically uses the current version:

# Run test environment with current version
./docker-test.sh

# The test container will display:
# πŸ“Œ Version: 1.0.0

CI/CD Integration

GitHub Actions

The repository includes GitHub Actions workflows for automated builds:

  1. On Push to Main: Builds and tags as latest
  2. On Git Tag: Builds with semantic version tags
  3. On Pull Request: Builds with PR number tag

Creating a Release

# 1. Update version
./scripts/version.sh minor

# 2. Commit changes
git add -A
git commit -m "chore: bump version to 1.1.0"

# 3. Create git tag
git tag v1.1.0

# 4. Push to GitHub
git push origin main
git push origin v1.1.0

# This triggers GitHub Actions to build and publish

Version Display

Health Endpoint

The version is exposed via the health endpoint:

curl http://localhost:3005/api/health

# Response includes:
{
  "version": "1.0.0",
  "gitCommit": "abc123",
  ...
}

Docker Image Inspection

# View image labels
docker inspect sasha-studio:latest | grep -A5 Labels

# View all tags
docker images sasha-studio

Build Information

After each build, a .last-build.json file is created with build details:

{
  "version": "1.0.0",
  "buildDate": "2024-01-11T10:00:00Z",
  "gitCommit": "abc123",
  "gitBranch": "main",
  "gitStatus": "clean",
  "tags": ["sasha-studio:1.0.0", "sasha-studio:latest"],
  "target": "runner"
}

Best Practices and Guidelines

Version Bumping Decision Tree

graph TD A[Change Made] --> B{Breaking Change?} B -->|Yes| C[MAJOR bump] B -->|No| D{New Feature?} D -->|Yes| E[MINOR bump] D -->|No| F{Bug Fix?} F -->|Yes| G[PATCH bump] F -->|No| H[No version change]

Version Bumping Guidelines

  1. PATCH - Bug fixes, security updates, dependency updates

    ./scripts/version.sh patch
    
  2. MINOR - New features, non-breaking changes

    ./scripts/version.sh minor
    
  3. MAJOR - Breaking changes, major refactors

    ./scripts/version.sh major
    

Pre-release Workflow

# Start beta phase
./scripts/version.sh prerelease beta

# Test and iterate
./scripts/version.sh prerelease beta  # -> beta.2, beta.3, etc.

# Move to release candidate
./scripts/version.sh prerelease rc

# Final release
./scripts/version.sh set 1.1.0

Development Builds

For development builds with uncommitted changes:

  • The build script automatically detects "dirty" state
  • Creates a unique dev tag with timestamp and commit
  • Example: 1.0.0-dev.20240111-143022.abc123

Registry Management

Local Registry

# Run local registry
docker run -d -p 5000:5000 --name registry registry:2

# Tag and push
docker tag sasha-studio:1.0.0 localhost:5000/sasha-studio:1.0.0
docker push localhost:5000/sasha-studio:1.0.0

GitHub Container Registry

# Set registry environment variable
export DOCKER_REGISTRY=ghcr.io/yourusername

# Build and push
./claudecodeui/scripts/docker-build.sh production

Rollback and Recovery

Quick Rollback Strategy

# List available versions
docker images sasha-studio --format "table {{.Tag}}\t{{.Created}}"

# Run specific version
docker run -p 3005:3005 sasha-studio:1.0.0

Version History

Keep track of deployed versions:

# Tag production deployments
docker tag sasha-studio:1.0.0 sasha-studio:prod-20240111

# View deployment history
docker images sasha-studio | grep prod-

Troubleshooting

Common Issues

  1. Version Mismatch

    # Sync package.json with VERSION file
    ./scripts/version.sh current
    
  2. Build Cache Issues

    # Clear Docker build cache
    docker builder prune -a
    
  3. Tag Conflicts

    # Force retag
    docker tag -f sasha-studio:new sasha-studio:latest
    

Version Verification

# Check VERSION file
cat VERSION

# Check package.json
grep version claudecodeui/package.json

# Check running container
curl http://localhost:3005/api/health | jq .version

# Check Docker image
docker inspect sasha-studio:latest | grep -i version

Migration from Old System

If migrating from an unversioned system:

  1. Determine current version based on features
  2. Create VERSION file: echo "1.0.0" > VERSION
  3. Tag existing images: docker tag sasha-studio:latest sasha-studio:1.0.0
  4. Update CI/CD pipelines to use new versioning
  5. Document version history in CHANGELOG.md

Changelog

Maintain a CHANGELOG.md file documenting version changes:

# Changelog

## [1.1.0] - 2024-01-11
### Added
- Semantic versioning system
- Automated Docker tagging
- Version display in health endpoint

### Changed
- Enhanced build scripts with version support
- Updated CI/CD workflows

### Fixed
- Build reproducibility issues

Summary and Benefits

What You've Learned

You now know how to:

  • Implement semantic versioning in any project
  • Create automated version management scripts
  • Integrate versioning with Docker builds
  • Set up CI/CD with proper version tagging
  • Handle pre-releases and release candidates
  • Maintain clear version history
  • Implement rollback strategies

Key Benefits of Proper Versioning

  1. Professional Standards: Follow industry best practices
  2. Clear Communication: Everyone knows what changed
  3. Easy Rollback: Quick recovery from issues
  4. Dependency Management: Other projects can depend on stable versions
  5. Automation Ready: Integrates with CI/CD pipelines
  6. Audit Trail: Complete history of changes

Next Steps

  1. Implement VERSION file in your project
  2. Create version management scripts
  3. Update build processes to use versions
  4. Set up CI/CD with version tags
  5. Start following semantic versioning rules
  6. Document your versioning strategy

Additional Resources


This guide provides a complete framework for implementing professional versioning in your software projects. Apply these principles consistently for better software management.