Fix four things when you create the archive: sort the file names, set one modification time for every member, record owner and group as numeric zero, and drop the extra pax timestamps. GNU tar can do all four with options. The same ideas apply to zip and other archive formats.
Why archives differ between runs
An archive records more than file contents. By default it can capture the last modification time, the order the filesystem returned the files in, the user and group that owned them, and their permissions. The Reproducible Builds documentation notes that file ordering is likely to be different on every run, because most formats read directories in the order the filesystem returns.
If your release process builds a tarball on two machines, any of these can change the checksum even though the files are identical.
The recommended GNU tar command
The project's recommended form needs GNU tar 1.28 or later:
tar --sort=name \
--mtime="@${SOURCE_DATE_EPOCH}" \
--owner=0 --group=0 --numeric-owner \
--pax-option=exthdr.name=%d/PaxHeaders/%f,delete=atime,delete=ctime \
-cf product.tar build
Each part has a job.
| Option | What it removes |
|---|---|
| sort by name | Filesystem listing order |
| mtime from SOURCE_DATE_EPOCH | The current time and per file times |
| owner 0, group 0, numeric owner | Build account names and ids |
| pax option deleting atime and ctime | Access and change times in extended headers |
If you use tar 1.29 or later, the clamp mtime option sets the time only for files newer than your chosen value, which leaves older original times untouched. For permissions, a mode option can force a uniform setting when different umask values on build machines change modes.
Older tar, zip and git archive
For older tar versions, the documentation shows listing files with find, sorting them with the C locale, and feeding the list to tar. The locale matters, because sort order differs between locales.
For zip, the documentation suggests resetting modification times with touch before archiving, and using the no extra attributes option so the archive does not store multiple Unix timestamps. It also recommends unpacking with TZ set to UTC, since zip stores times without timezone information.
For git archive, the documentation says output is not guaranteed deterministic. The tar is typically stable, but the internal gzip is not. Piping to gzip with level 6 and the no name flag is more reliable.
Check your result
- Run the command twice from two different directories.
- Compare checksums with sha256sum.
- If they differ, compare the archives with diffoscope and fix the first cause.
- Add the check to CI so a regression shows up on the pull request that causes it.
Try it yourself
A reproducible tarball makes your release checksum meaningful, but you still need to know what is inside. Safeguard's SBOM and dependency scanning is generally available, and you can run the free scan at gold.safeguard.sh/scan. The demo shows a dependency inventory; look for how each component is listed with its version.
Click through it at your own pace, or open it full screen.
Quick check
Is setting only the modification time enough?
Usually not. File order, owner names and extended pax headers can still differ between runs.
Why record owner as 0?
Builds often run as regular users, and recording their names or ids leaks the build account into the archive.
Sources
- Reproducible Builds: Archive metadata
- Reproducible Builds: Stable order for inputs
- Reproducible Builds: SOURCE_DATE_EPOCH specification
Last reviewed: 19 October 2026.