Stamp builds with something that identifies the source, such as a release tag plus the full commit id, and take any date from the commit or changelog. Avoid the current time and incrementing build counters, because they make two builds of the same source differ.
Why the build date is a poor label
Teams often write the build time into the binary or the About screen as a quick way to tell versions apart. The reproducible-builds documentation explains why that stops working: recording the time of the build is meaningless once builds are reproducible, because old source can always be compiled later. It recommends tracking the source more accurately than a timestamp and extracting any needed date from version control or a changelog.
The same guidance names counter examples directly: the current date or an incremental build counter.
What to use instead
| Label | Deterministic? | Notes |
|---|---|---|
| Current date and time | No | Differs on every build |
| CI build number | No | Same source, different number on each rerun |
| Release tag | Yes | Good for humans, can be moved if tags are not protected |
| Full commit id | Yes | Identifies the exact source |
| Commit date | Yes | Gives users a sense of age |
A common pattern is a tag for people and the full commit id for machines, shown together, for example a version number followed by the full id in a build information file.
The short hash trap
Developers like the short form of a commit id. The reproducible-builds documentation warns that abbreviated hashes can be a source of non-reproducibility. The reason is that the abbreviation length depends on the number of objects in the repository. That number changes over time, and it is much smaller in a shallow clone, which many CI systems use by default. The same commit can therefore produce a seven character id in one place and a longer one in another. The documentation notes the Git setting for this and the fix is to use the full identifier or set a fixed length.
# full id, stable everywhere
git rev-parse HEAD
# date of the commit, in a fixed format
git log -1 --format=%cI
Feed the date to tools that want one
Some tools write a date whatever you do. The project standardised an environment variable called SOURCE_DATE_EPOCH, which holds seconds since the Unix epoch and is meant to be read by build tools in place of the current time. Set it from the commit time and tools that support it will use it.
export SOURCE_DATE_EPOCH=$(git log -1 --pretty=%ct)
Steps to change an existing build
- Search for places that print the date, a build number or a hostname into output files.
- Replace them with the tag, full commit id and commit date.
- Set SOURCE_DATE_EPOCH for tools that honor it.
- Make sure CI fetches enough history for tag lookups, or pass the version in explicitly.
- Build twice and compare hashes to confirm the change worked.
Where Safeguard fits
A precise source identifier also helps when you match an SBOM to a release. Safeguard generates SBOMs and runs dependency scanning, and a free scan is at gold.safeguard.sh/scan.
Quick check
Can I still show a build date in the app?
Yes, if it comes from the commit or changelog. That date is the same on every rebuild of that source.
Do CI build numbers have any use?
Yes, for tracking runs in your CI system. Just keep them out of the shipped artifact.
Sources
- Reproducible Builds: Version information
- Reproducible Builds: Timestamps
- Reproducible Builds: SOURCE_DATE_EPOCH
Last reviewed: 19 October 2026.