Version Artifacts & Releases
Learn to apply versioning to artifacts and releases in CI/CD. This lesson covers the core concept, hands-on steps, comparison of options, and troubleshooting edge cases, preparing you for the next lesson in the track.
Focus: apply versioning to artifacts and releases
You've built your pipeline, tests pass, and your deployment is running smoothly. But then comes the inevitable moment: something breaks in production, and you need to figure out which version of your code is actually deployed. Or worse, you attempt to roll back, only to realize you have no idea what's in that artifact named app-latest.jar. You're not alone — this is the pain that applying versioning to artifacts and releases solves. Without a clear, consistent versioning strategy, you're flying blind: debugging becomes guesswork, releases are a mess, and your team loses trust in the pipeline.
In this lesson, you'll learn how to apply versioning to artifacts and releases in your CI/CD pipeline. We'll cover the core concepts, walk through a hands-on example, compare different versioning schemes, and troubleshoot common pitfalls. By the end, you'll be able to confidently identify, trace, and manage every artifact and release your pipeline produces.
The problem this lesson solves
Imagine you're on-call and a user reports a regression after a new deployment. You look at the production environment and see a tag like v1.4.2. You check your container registry and find images labeled latest — but which one is v1.4.2? In a rush, you deploy what you think is the right image, only to make things worse. This scenario—versioning chaos—is all too common.
Without versioning, you face:
- Untraceable artifacts: A JAR or Docker image named
final_v2.jardoesn't tell you which commit it came from or when it was built. - Failed rollbacks: You can't roll back to a known-good version if you don't know what's deployed.
- Audit nightmares: For regulated industries, you need to prove what code is running where — impossible with random filenames.
- Broken concurrency: Multiple developers pushing to the same artifact name cause overwrites and confusion.
Applying versioning to artifacts and releases means giving each artifact a unique, meaningful identifier that ties it to its source code and build metadata. This turns your pipeline from a black box into a transparent, auditable system.
Core concept / mental model
Think of versioning as the ID card for your software. Just as a passport identifies a person with a unique number, a versioned artifact identifies a specific build with a unique tag. This tag should encode enough information to answer three questions:
- What code is this? — The commit hash or branch.
- When was it built? — The timestamp or build number.
- What stage is it? — Is it a release candidate, stable release, or hotfix?
In practice, we use semantic versioning (SemVer) as the backbone: MAJOR.MINOR.PATCH (e.g., 2.1.0). To this, we append build metadata, like a commit hash or build number: 2.1.0+build.47 or 2.1.0-rc.1. This makes every artifact unique and sortable.
The mental model: source code is the blueprint, build is the factory, and the artifact is the product. Versioning is the label that says which blueprint produced this product, and the release is the official announcement of the product being available to users.
How it works step by step
Applying versioning involves a few key steps in your CI/CD pipeline:
-
Define your versioning scheme — Decide whether you'll use SemVer only, or append build metadata. Most teams use SemVer for releases and add build metadata for traceability.
-
Determine the version at build time — In CI, you can derive the version from: - A tag on the commit (e.g.,
v1.2.0). - The branch (e.g.,main→ release candidate,feature-x→ snapshot). - A build number from the CI system (e.g., GitHub Actions run number). -
Tag the artifact — When you produce the artifact (JAR, Docker image, npm package), assign the version as a tag or filename. For Docker, use tags like
registry.example.com/myapp:1.2.0and:1.2.0+sha256:.... -
Publish and record metadata — Store the version in a manifest (e.g.,
version.txtin the image, or a release note) and link it to the commit SHA and build URL. -
Create a release — On GitHub, a release is a tagged commit with notes. This release points to the artifact — you don't store the binary in the repo, but you link to it in the registry.
-
Use versioned references in deployments — Your deployment manifests (Kubernetes YAML, Terraform, etc.) should reference the exact version, never
latest.
Hands-on walkthrough
Let's apply versioning in a GitHub Actions workflow. We'll create a simple Node.js app, build it, and publish a versioned Docker image.
Step 1: Create the workflow
Create .github/workflows/release.yml:
name: Build and Publish
on:
push:
tags:
- 'v*'
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Extract version from tag
id: version
run: echo "VERSION=${GITHUB_REF_NAME#v}" >> $GITHUB_OUTPUT
- name: Build the app
run: |
npm install
npm run build
echo "${{ steps.version.outputs.VERSION }}" > build/version.txt
- name: Publish Docker image
uses: docker/build-push-action@v5
with:
push: true
tags: |
ghcr.io/${{ github.repository }}:${{ steps.version.outputs.VERSION }}
ghcr.io/${{ github.repository }}:${{ github.sha }}
This workflow triggers only when you push a tag like v1.2.0. It extracts the version (without the v), builds the app, writes version.txt, and pushes the Docker image tagged with both the semantic version and the commit SHA.
Step 2: Create a release
After the workflow succeeds, create a release on GitHub that points to that tag. Use gh CLI:
gh release create v1.2.0 --generate-notes
The release will include notes and link to the Docker image.
Step 3: Deploy using the version
In your Kubernetes deployment:
spec:
containers:
- name: app
image: ghcr.io/your-org/myapp:1.2.0
Now every deployment references a specific, reproducible artifact.
Expected output: When you run docker images locally (if you pulled it), you see tags like 1.2.0 and a unique SHA. In your registry, you can fetch the exact image by tag.
Compare options / when to choose what
There are several versioning strategies. Here's how they compare:
| Strategy | Pros | Cons | Best for |
|---|---|---|---|
SemVer only (1.2.0) |
Human-readable, standards-based | Can collide if multiple builds have same version | Release versions |
SemVer + build metadata (1.2.0+build.47) |
Unique, traceable | Slightly verbose | Continuous builds |
Commit SHA only (abc1234) |
Guaranteed unique, precise | Not human-friendly, can't sort semantically | Dev/test environments |
Date-based (2025.04.01) |
Intuitive chronological order | No semantic meaning, can collide | Nightly builds |
For production releases, use SemVer + build metadata or SemVer + SHA tag. For pre-release, append -rc.1 or -alpha. Avoid using latest outside of dev.
Troubleshooting & edge cases
-
Issue: My artifact version looks like
1.2.0+build.47+commit— is that valid? SemVer only allows one+for build metadata. Use dashes for pre-release, but metadata can't have+in it. Encode multiple numbers with dots or use a separate label like1.2.0+build47.commit. Always validate withnpx semveror a similar tool. -
Issue: The same version gets overwritten in the registry. If you push a tag twice, you can overwrite an existing image. To avoid this, ensure your CI only runs on unique tags (e.g., trigger on tag creation) and consider setting the registry to immutable tags (available in most registries).
-
Issue: The release points to a different commit than expected. Double-check that your tag is attached to the correct commit. When you create a release via
gh release create, it defaults to the tag's commit, but if you manually create a release at a different ref, it will mismatch. -
Edge case: Hotfix branches — For a hotfix, you should still create a tag like
v1.2.1from the hotfix branch. Your workflow should handle any tag pattern; just ensure your build is reproducible from that commit.
What you learned & what's next
You now understand why versioning matters and how to apply it. You learned that:
- Artifacts need unique, meaningful versions tied to source.
- Semantic versioning is the standard, with build metadata for traceability.
- CI/CD pipelines must generate and attach versions consistently.
- Deployments should reference exact versions, not
latest.
You've completed a hands-on exercise: a workflow that tags a Docker image with both a SemVer version and a commit SHA, and you know how to create a release.
The next lesson in this track will cover promoting artifacts between environments, showing you how to use these versions to move from staging to production safely. You'll build on the versioning you just learned to implement promotion gates and approvals.
Keep your artifacts labeled, your releases traceable, and your pipeline reliable — versioning is the glue that holds it all together.
Practice recap
Create a new GitHub Actions workflow in a practice repository that builds a simple static site and uploads the artifact to a release. Tag a commit with v0.1.0 and verify that the release contains a file named site-v0.1.0.zip. Then modify the workflow to also push a Docker image tagged with 0.1.0 and your commit SHA. This hands-on exercise will solidify the versioning flow you just learned.
Common mistakes
- Using
latestas a tag in production — always reference a specific version to avoid ambiguity. - Not including build metadata — if two builds share the same version, you can't distinguish them.
- Forgetting to add a version file or manifest inside the artifact — the tag alone isn't enough for runtime introspection.
- Mixing unrelated versioning schemes across projects — consistency is key for tooling and humans.
Variations
- Using CI-provided build numbers (e.g., GitHub Actions run ID) as the build metadata instead of a commit SHA.
- Using
-SNAPSHOTfor branch builds (Maven-style) and SemVer for releases. - Storing the version in the image label (OCI) or artifact metadata rather than in the filename.
Real-world use cases
- Kubernetes deployment rolling back to a previous Docker image by tag, e.g.,
image: myapp:1.2.0. - Auditing production for compliance — linking artifacts to Git commits via version manifests.
- Publishing a public library to npm with semantic versioning and build metadata for every release.
Key takeaways
- Versioning provides traceability from code to artifact to deployment.
- Semantic versioning (MAJOR.MINOR.PATCH) is the industry standard for releases.
- Always append build metadata like commit SHA or build number to make artifacts unique.
- Automate version generation in CI/CD to avoid manual errors.
- Deployments must pin exact versions, never
latest. - Releases are tied to tags; keep the tag and artifact version consistent.
Keep learning
Related tutorials, quizzes, and articles for this topic.
Discussion
Questions, corrections, and tips help everyone reading this page.
0 comments
Add a comment
No comments yet — start the thread.