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
| Version | Component | When to Increment | Example |
|---|---|---|---|
| X.0.0 | MAJOR | Breaking changes | API changes, removed features |
| 1.X.0 | MINOR | New features (compatible) | New endpoints, new options |
| 1.0.X | PATCH | Bug fixes (compatible) | Security patches, bug fixes |
Examples in Practice
Starting version: 1.0.0
| Change | New Version | Why |
|---|---|---|
| Fixed a bug | 1.0.1 | Patch - bug fix only |
| Added new optional parameter | 1.1.0 | Minor - new feature, backwards compatible |
| Removed deprecated API | 2.0.0 | Major - breaking change |
| Fixed security vulnerability | 1.0.2 | Patch - bug fix (even if important) |
Added new endpoint /api/v1/users | 1.2.0 | Minor - new feature |
| Changed required field from string to number | 2.0.0 | Major - 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.0for 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.0 → 1.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.1 → 1.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.0 → 2.0.0
Part 4: Pre-Release Versions
Alpha, Beta, and Release Candidates
Pre-release versions append a label: MAJOR.MINOR.PATCH-label.number
| Label | Meaning | Stability | Example |
|---|---|---|---|
| alpha | Early testing, unstable | Very low | 2.0.0-alpha.1 |
| beta | Feature complete, testing | Medium | 2.0.0-beta.1 |
| rc (Release Candidate) | Final testing before release | High | 2.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:
| Tag | Purpose | Example | When to Use |
|---|---|---|---|
latest | Latest stable release | myapp:latest | Development |
MAJOR.MINOR.PATCH | Exact version | myapp:2.1.3 | Production (pin exact version) |
MAJOR.MINOR | Auto-update patches | myapp:2.1 | Staging (get patches automatically) |
MAJOR | Auto-update minor | myapp:2 | Development (brave) |
COMMIT-SHA | Specific build | myapp:abc123f | Debugging/rollback |
NPM/Package Versions
In package.json:
Dependency version ranges:
| Prefix | Meaning | Example | Allows |
|---|---|---|---|
^ (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 |
| None | Exact version | 1.2.3 | Only 1.2.3 |
Quick Reference
Version Bump Decision Matrix
| Change Type | Example | Version Bump |
|---|---|---|
| Fix a bug | Fixed crash on empty input | PATCH (1.2.3 → 1.2.4) |
| Add optional parameter | New optional `limit` param | MINOR (1.2.3 → 1.3.0) |
| Add new endpoint | `POST /api/export` | MINOR (1.2.3 → 1.3.0) |
| Remove endpoint | Deleted `/api/old` | MAJOR (1.2.3 → 2.0.0) |
| Change required field type | `age` from string to number | MAJOR (1.2.3 → 2.0.0) |
| Rename function | `getUserData` → `getUser` | MAJOR (1.2.3 → 2.0.0) |
| Add new response field | Added `createdAt` field | MINOR (1.2.3 → 1.3.0) |
| Change response structure | New error format | MAJOR (1.2.3 → 2.0.0) |
| Deprecate (but not remove) | Mark `oldMethod` deprecated | MINOR (1.2.3 → 1.3.0) |
| Security patch | Fix SQL injection | PATCH (1.2.3 → 1.2.4) |