Tutorial: Version Your Project with Semantic Versioning

In this tutorial, you’ll learn how to version your project using Semantic Versioning (SemVer), create meaningful Git tags, and automate versioning with GitLab CI/CD.

What you’ll learn: How to choose version numbers, tag releases, and manage versions across Git, Docker, and npm.

Prerequisites:

  • A Git repository
  • Basic understanding of Git commands

Why Semantic Versioning?

The Problem

Without a versioning standard:

  • Users don’t know if updates are safe
  • Breaking changes surprise everyone
  • Dependency management becomes impossible
  • “Version 2.3.7.1.beta.final.REALLY_FINAL” exists
  • No clear upgrade path

The Solution

Semantic Versioning (SemVer) provides:

  • Clear communication of change impact
  • Safe upgrades when you know the rules
  • Dependency resolution that works
  • Universal standard everyone understands
  • Automated tooling support

Part 1: Understanding SemVer

The Three Numbers

VersionComponentWhen to IncrementExample
X.0.0MAJORBreaking changesAPI changes, removed features
1.X.0MINORNew features (compatible)New endpoints, new options
1.0.XPATCHBug fixes (compatible)Security patches, bug fixes

Examples in Practice

Starting version: 1.0.0

ChangeNew VersionWhy
Fixed a bug1.0.1Patch - bug fix only
Added new optional parameter1.1.0Minor - new feature, backwards compatible
Removed deprecated API2.0.0Major - breaking change
Fixed security vulnerability1.0.2Patch - bug fix (even if important)
Added new endpoint /api/v1/users1.2.0Minor - new feature
Changed required field from string to number2.0.0Major - breaks existing code

Decision Tree

Part 2: Your First Version Tag

Step 1: Decide Your Starting Version

For new projects:

  • Start at 0.1.0 (still in development)
  • Use 1.0.0 for first stable release

For existing projects without versions:

  • If production-ready and stable: 1.0.0
  • If still evolving: 0.x.0

Step 2: Create Your First Git Tag

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

Commit this:

Part 3: Versioning Through Development

Scenario 1: Bug Fix (Patch Release)

Situation: You fixed a bug where dates were incorrectly formatted.

Change type: Bug fix, no new features, backwards compatible

Version bump: 1.0.01.0.1

Scenario 2: New Feature (Minor Release)

Situation: You added a new API endpoint for exporting data.

Change type: New functionality, backwards compatible (old code still works)

Version bump: 1.0.11.1.0

Scenario 3: Breaking Change (Major Release)

Situation: You changed the API response format from {status, data} to {success, result}.

Change type: Breaking change - existing clients will break

Version bump: 1.1.02.0.0

Part 4: Pre-Release Versions

Alpha, Beta, and Release Candidates

Pre-release versions append a label: MAJOR.MINOR.PATCH-label.number

LabelMeaningStabilityExample
alphaEarly testing, unstableVery low2.0.0-alpha.1
betaFeature complete, testingMedium2.0.0-beta.1
rc (Release Candidate)Final testing before releaseHigh2.0.0-rc.1

Example: Releasing Beta Versions

Pre-release version ordering:

Part 5: Tagging Strategy for Different Contexts

Git Tags

Format: vMAJOR.MINOR.PATCH

Best practice: Use the v prefix for Git tags to distinguish from other tags.

Docker Tags

Multiple tags per image:

Recommended Docker tagging strategy:

TagPurposeExampleWhen to Use
latestLatest stable releasemyapp:latestDevelopment
MAJOR.MINOR.PATCHExact versionmyapp:2.1.3Production (pin exact version)
MAJOR.MINORAuto-update patchesmyapp:2.1Staging (get patches automatically)
MAJORAuto-update minormyapp:2Development (brave)
COMMIT-SHASpecific buildmyapp:abc123fDebugging/rollback

NPM/Package Versions

In package.json:

Dependency version ranges:

PrefixMeaningExampleAllows
^ (caret)Compatible versions^1.2.3>=1.2.3 <2.0.0
~ (tilde)Patch updates only~1.2.3>=1.2.3 <1.3.0
NoneExact version1.2.3Only 1.2.3

Quick Reference

Version Bump Decision Matrix

Change TypeExampleVersion Bump
Fix a bugFixed crash on empty inputPATCH (1.2.3 → 1.2.4)
Add optional parameterNew optional `limit` paramMINOR (1.2.3 → 1.3.0)
Add new endpoint`POST /api/export`MINOR (1.2.3 → 1.3.0)
Remove endpointDeleted `/api/old`MAJOR (1.2.3 → 2.0.0)
Change required field type`age` from string to numberMAJOR (1.2.3 → 2.0.0)
Rename function`getUserData` → `getUser`MAJOR (1.2.3 → 2.0.0)
Add new response fieldAdded `createdAt` fieldMINOR (1.2.3 → 1.3.0)
Change response structureNew error formatMAJOR (1.2.3 → 2.0.0)
Deprecate (but not remove)Mark `oldMethod` deprecatedMINOR (1.2.3 → 1.3.0)
Security patchFix SQL injectionPATCH (1.2.3 → 1.2.4)

Common Commands Cheatsheet

Additional Resources