Version Control¶
How App-name versions, displays, and releases itself. The scheme is plain semantic versioning, the version string is shown in the UI alongside the environment, and pushing a tag is what ships production.
Semantic versioning scheme¶
Versions follow MAJOR.MINOR.PATCH.
| Bump | When | Examples |
|---|---|---|
MAJOR (1.0.0 → 2.0.0) |
Breaking changes | Incompatible API changes, removed features, breaking schema migrations |
MINOR (1.0.0 → 1.1.0) |
Backwards-compatible features | New endpoints, new screens, new dashboard widgets |
PATCH (1.0.0 → 1.0.1) |
Backwards-compatible fixes | Bug fixes, performance work, security patches |
No non-semantic versions
Always three numeric segments. Never 1.0.1.2 or date-based strings — the git tag, the display string, and the package version must all agree.
Where the version lives¶
The single source of truth is frontend/package.json:
{
"version": "1.0.0"
}
The frontend reads this at build time and exposes it through a small version helper that the UI consumes. The git tag (vX.Y.Z) must match the package.json version for every release.
How the app displays version and environment¶
The UI shows the version in two places:
- Login page — the version string under the sign-in card.
- Application footer — fixed at the bottom of every authenticated page.
The environment is derived from the hostname, and a badge is shown for every non-production environment so it is always obvious where you are.
| Hostname | Environment | Badge |
|---|---|---|
localhost / 127.0.0.1 |
Development | shown |
test.<your-domain> |
Test | shown |
<your-domain> |
Production | hidden |
Hovering the version reveals the build time (see below). In production with no build time, it falls back to a "Development Build" label.
How build time is injected via Docker¶
The build timestamp is not committed — it is injected at image-build time by CI and baked into the frontend bundle as a Vite environment variable.
- name: Set build timestamp
id: build-time
run: echo "BUILD_TIME=$(date -u +"%Y-%m-%dT%H:%M:%SZ")" >> "$GITHUB_OUTPUT"
- name: Build and push frontend image
uses: docker/build-push-action@v6
with:
build-args: |
VITE_BUILD_TIME=${{ steps.build-time.outputs.BUILD_TIME }}
The Dockerfile accepts the build arg and promotes it to a VITE_-prefixed variable so Vite inlines it:
ARG VITE_BUILD_TIME
ENV VITE_BUILD_TIME=$VITE_BUILD_TIME
Local builds have no build time
Running npm run dev locally leaves VITE_BUILD_TIME unset, which is why the footer shows "Development Build". This is expected.
The release workflow¶
Releasing is four steps: bump, commit, tag, push. Pushing the tag is what triggers the production deploy — see Deploy to Production.
# 1. Bump the version in frontend/package.json (e.g. 1.0.0 -> 1.1.0)
# 2. Commit the bump
git add frontend/package.json
git commit -m "Release v1.1.0: <summary of changes>"
# 3. Tag the release (annotated)
git tag -a v1.1.0 -m "Release v1.1.0"
# 4. Push the branch and the tag
git push origin main
git push origin v1.1.0
What happens next:
sequenceDiagram
participant Dev
participant GH as GitHub
participant CI as GitHub Actions
participant Prod as Production
Dev->>GH: push main
GH->>CI: build, test, scan, deploy
CI->>CI: deploy to test (automatic)
Dev->>GH: push tag vX.Y.Z
GH->>CI: production deploy (awaits approval)
CI->>Prod: build image with VITE_BUILD_TIME, deploy
Prod-->>Dev: vX.Y.Z live, no env badge
Verify the release
After production deploys, confirm the login page and footer show the new vX.Y.Z, the environment badge is absent in production, and the build time is recent.
Version rollback¶
Rolling back is re-deploying a previous tag — the version string and image follow the tag, so there is nothing to hand-edit on the host.
- Identify the last known-good tag:
git tag --list 'v*' --sort=-v:refname. - Re-run the production deploy for that tag (re-trigger the deploy workflow for
vX.Y.Z, approving the gate). - If the rollback should become the new baseline, cut a fresh PATCH release from the good commit rather than leaving production on an older tag.
Editing version in production does nothing
The version is compiled into the frontend bundle at build time. Changing files on the host has no effect until a new image is built and deployed — always roll back through a tag and a rebuild.
Best practices¶
| Do | Don't |
|---|---|
Keep the git tag and frontend/package.json in sync |
Skip the version bump on a release |
| Use annotated tags with descriptive messages | Use non-semantic versions |
| Verify on test before tagging production | Deploy straight to production untested |
| Roll back via a tag + rebuild | Hand-edit version files on the server |
See New Project Setup for cutting the very first v1.0.0, and Pipeline Overview for how the deploy stages fit together.