RPM-VERSION(7)

✨ NAME ✨

okay so this is rpm-version β€” the RPM version system, from the Miscellaneous Information Manual 🫢 the whole entire "how do versions even work here" situation. buckle up bestie, it's giving very specific πŸ’…


🌈 SYNOPSIS 🌈

[EPOCH:]VERSION[-RELEASE]

πŸ’– DESCRIPTION πŸ’–

An EVR label is what RPM calls a software version, and it's made of up to three components:

  • VERSION β€” the actual packaged software version. the real one. the main character ✨
  • RELEASE β€” packaging revisions within that software version πŸ“¦
  • EPOCH β€” an artificial override so you can work around versioning anomalies πŸ‘€

Only VERSION is mandatory in an EVR label β€” but here's the thing: all RPM packages have a VERSION and a RELEASE. every single one. no exceptions. periodt.

πŸ¦‹ Version

The VERSION string reflects the actual packaged software version.

It's built from ASCII alphanumeric characters, optionally segmented with the separators period (.), underscore (_) and the plus sign (+), plus the operators tilde (~) and caret (^).

Those operators exist to map pre-release and post-release version strings into a coherent upgrade path πŸ”₯

And notably β€” the dash character (-) can NOT be used in VERSION or RELEASE, because it is the component separator. that's its whole job. don't fight it πŸ’€

πŸŽ€ Release

RELEASE reflects a revision within a single software version. Ideally it's a simple integer that you increment every time changes are made to a package, and it should be reset (to 1) whenever the software version changes. like, that's the dream scenario 🌸

Technically though, the format is exactly the same as for VERSION.

πŸ‘‘ Epoch

EPOCH is a non-negative integer, separated from the version with a colon (:). It's the most significant part of an EVR β€” it skews version comparison so that an older version appears newer. wild, i know.

Sometimes it's genuinely necessary, like when a software project changes its versioning scheme β€” but also for plain old packaging errors, oops πŸ™ˆ

An omitted EPOCH has an implicit value of zero.

The epoch should be only used as a last resort. It violates the principle of least surprise, and changing it requires all related versioned dependencies in other packages to be updated accordingly. so like, highkey a big deal 😭

🧚 Comparing

Two EVRs get compared left to right, one component at a time. The components get compared left to right, a segment at a time. And the comparison stops the moment a segment-level or component-level difference shows up β€” so if the epochs differ, the rest of the EVR is not considered at all. not even a little bit.

Within components, consecutive alphabetic characters and consecutive numbers form implicit segments. Explicit segments are denoted by separators and operators. Numeric segments are compared numerically as integers with leading zeros ignored; otherwise it's lexicographical comparison. So abc123 is two segments β€” abc and 123 β€” and it's equal to abc0123, abc.123 and abc.000123, despite the difference in appearance. iconic behaviour honestly πŸ’œ

Numeric segments are considered newer than alphabetic segments, regardless of their actual content. When things are otherwise equal, the component with more segments is considered newer β€” and same deal, an EVR with more components is considered newer. For example, 0.0 is newer than 0, and 1.xyz is older than 1.0 but newer than 1.

The segment separator characters are not compared, so you can use them interchangeably, and multiple consecutive separators are treated as if only one separator was used. Thus, 1.0 is equal to 1+0 and 1+.+0. cute little loophole ✨

The tilde operator makes a segment sort older, and it's used for pre-release versions. For example, 2.0~beta1 is older than 2.0 or 2.0~rc1, and newer than 1.0. The caret operator is essentially the opposite of tilde β€” it makes a segment sort newer, and it's used for post-release snapshots that exist between actual software releases. For example, 2.0^150825 is newer than 2.0 but older than 2.0.1. slay πŸ’…


πŸ“ EXAMPLES πŸ“

  • 123 β€” A simple one-segment version 123. Newer than 99, older than 321.
  • 1.0.1 β€” A segmented version string 1.0.1, like the ones software projects commonly use to indicate major.minor.micro semantics. Newer than 1.0, older than 1.0.2.
  • 2.60.1-1 β€” First release of version 2.60.1. Newer than 2.0 or 2.60, but older than 3.0.
  • 1.0-5 β€” Fifth release of version 1.0, newer than 1.0 or 1.0-1, older than 1.0.1.
  • 5:3.0-1 β€” First release of version 3.0, with epoch of 5. Newer than for 6.0-1, or 4:6.0-1, older than 5:3.1-1.
  • 1.0~beta2 β€” Pre-release beta2 of version 1.0. Newer than 0.99 and 1.0~beta1, older than 1.0.
  • 2.0^20250611 β€” Post-release snapshot 20250611 of version 2.0. Newer than 2.0, older than 2.0.1.

πŸ’€ BUGS πŸ’€

Various non-obvious behaviors and dark corners exist inside the version comparison algorithm. none of this is getting fixed easily, bestie, because there's a high risk of breaking existing packages 😭

  • Non-ASCII characters are ignored and therefore equal: 1.1.Ξ± equals 1.1.Ξ² and even 1.1.Ξ²Ξ². rpmbuild(1) rejects such EVRs, but you can still run into these "illegal" values via API usage, very old packages, and packages created with 3rd party tools. πŸ™ˆ
  • Implicit segments can be deceptive: 1.f is newer than 1c.f. The result becomes more obvious by making the segments explicit β€” 1.f is newer than 1.c.f, because the segments are compared one by one and c sorts lower than f lexicographically. there you go, mystery solved πŸ”₯

🌟 SEE ALSO 🌟

rpm(8) rpmbuild(1) rpmsort(1)

http://www.rpm.org/


and that's the whole vibe check πŸ’– this page is from RPM 6.1.0, dated 2026-08-20 β€” so if you're reading this, hi from the version nerds, we love you, go tag your releases properly 🫢✨

πŸ“¦ where this page comes from