On-prem and self-hosted release notes: writing for operators who upgrade on their own schedule

Most of this library assumes you operate the software you ship: you deploy, everyone is on the new version, and the changelog announces what already happened. On-prem and self-hosted products invert that. You cut a release; a stranger’s platform team decides — weeks or months later, on their schedule, inside their change process — whether to install it. GitLab self-managed, Keycloak, Sentry, Mattermost, Proxmox: for products like these the release notes aren’t an announcement, they’re an evaluation document. An operator reads them twice — once when the release lands, to decide whether it matters, and again weeks later, line by line, while writing the upgrade ticket. Write for the second reading.

Your reader is planning a change window

In a company that self-hosts, an upgrade is a small project: a ticket, a maintenance window negotiated with the teams who depend on the system, often a change-approval step, a rehearsal on staging, and a rollback plan that someone will actually ask to see. The person doing that work comes to your release notes with four questions, in this order:

  • Is the upgrade path from my version supported? Not “can I install 8.4” — “can I get there from 8.1, and do I have to stop anywhere on the way?”
  • Does it require downtime, and how much? A rolling upgrade and a four-hour outage are different projects with different approval chains.
  • Can I roll back if it goes wrong? And until when — before the schema migration is finalized? Never?
  • What do I have to change before or after? Config keys, reverse-proxy rules, integrations, hardware sizing.

The discipline that follows: every release entry carries a short Upgrade notes block that answers all four — especially when the answers are boring. “No downtime, no config changes, rollback supported” takes one line and turns a risk assessment into a checkbox. When the block is missing, the operator can’t tell “nothing to report” from “nobody wrote it down,” and either assumes the worst or opens a support ticket to ask — both more expensive than the sentence would have been.

Everyone is a version-skipper

SaaS users are always on the latest version. Self-hosting operators are deliberately behind — they batch upgrades into quarterly windows, skip releases that don’t affect them, and jump LTS to LTS. That changes how notes get read: when someone plans the jump from 8.1 to 8.4, they read every entry in between in one sitting. Two consequences:

Entries must stand alone and share a skeleton. The same sections in the same order, release after release, turn a twelve-entry read into a scan — the operator reads four Upgrade notes blocks and knows the shape of the whole jump. Essay-shaped entries turn it into an afternoon of archaeology.

The cumulative path lives in docs; the changelog links to it. If jumps have required stops (“upgrades from 8.1 must pass through 8.2” — GitLab publishes exactly this as required upgrade stops), maintain one always-current upgrade-path page in your documentation and link it from every entry’s Upgrade notes line. That’s the docs/changelog division of labor: the changelog is the dated record of each release, the docs page is the current answer to “how do I get from anywhere to here.” Don’t paste the path into each entry — pasted copies diverge the day you add a stop.

The operational facts that belong in every entry

Beyond the usual added/fixed/changed content, an installable product’s entry has a second audience — the person responsible for the machine it runs on. These are the facts they need, and none of them are optional when true:

  • Downtime and duration. Rolling or not; if not, a realistic window. “Brief interruption” is not a duration.
  • Database migrations — sized honestly. Whether they run online or lock tables, roughly how long per unit of data (“about 10 minutes per 10 million rows”), and whether they’re reversible. The operator with 400 million rows needs to know they’re the exception before the window opens, not during it.
  • Rollback. Supported to which version, until what point, and what’s lost. “Rollback: not supported past this release” is a legitimate answer — stated, it changes how carefully people stage; unstated, it becomes a 2 a.m. discovery.
  • Config changes. Renamed keys, new required settings, changed defaults — with the old-key grace period if there is one.
  • Support-matrix changes. Dropping an OS version, a database version, a CPU architecture, or an LDAP server you used to tolerate is a breaking change for whoever runs on it, even though no API changed. It gets the same loud treatment and the same deprecation window — announced releases ahead, never discovered at install time.
  • Resource and dependency shifts. Meaningfully higher memory floor, a bundled runtime bump (embedded Java, Node, PostgreSQL) — anything that changes what the host must provide.

Put together, the skeleton looks like this:

v8.4.0 — 2026-07-14

Highlights
- SCIM provisioning for groups — directory teams stop syncing by hand
- Fixed: LDAP sync dropping nested groups deeper than three levels

Upgrade notes
- Supported from: 8.2 or later (older installs: upgrade to 8.2 first — see upgrade path)
- Downtime: none (rolling). Online DB migration, ~10 min per 10M audit rows
- Rollback: to 8.3 until the finalize step runs (step 4 of the upgrade guide)
- Config: auth.session_ttl renamed to auth.session.ttl — old key honored until 9.0
- Support matrix: PostgreSQL 13 support removed (deprecated in 8.0, 12 months ago)

LTS branches: one fix, three changelogs

Sooner or later an installable product grows long-term-support branches, because enterprises pay for the right to stay put. Now a single bug fix lands in three places — 9.1.2, 8.4.7, 8.2.15 — and your changelog has to keep the story straight:

  • Give each supported branch its own stream (or an unmistakable per-branch label). An operator pinned to the 8.2 LTS reading 9.x notes can’t tell what applies to them; worse, they’ll assume a fix reached their branch when it didn’t.
  • State every fixed version, every time. “Fixed in 9.1.2, 8.4.7, and 8.2.15” — one line, and each branch’s readers know where they stand. If a branch deliberately doesn’t get the fix, say that too: a stated decision reads as policy, a gap reads as neglect.
  • Publish the branch policy once — what LTS receives (security fixes only? critical bugs?), how long each branch lives, and its end-of-support date. Then announce that date the way you’d announce any sunset: at LTS launch, at reminders, and in-band — never for the first time in the branch’s final release.

Security fixes you can’t apply for them

The heaviest difference from SaaS: you can’t patch your users. Every unapplied security fix keeps living on customer infrastructure, and the only tool you have is communication. That raises the bar on precision:

  • Exact affected ranges, exact fixed versions, per branch. Not “upgrade to the latest” — “affects 8.0.0–8.4.6 and 8.2.0–8.2.14; fixed in 8.4.7 and 8.2.15.” Vulnerability scanners and customers’ compliance teams consume these ranges mechanically; imprecise ranges generate false positives that land on your support queue.
  • Severity honest, mechanism vague, until adoption. The two-document pattern applies with more force than usual, because the adoption window isn’t hours — it’s weeks of other people’s change management. Ship-day entry: what an attacker gains, who’s affected, how urgent. Full advisory: after installs have had a real chance to move.
  • Never patch silently. Customers diff releases, and a fix that appears in the diff but not the notes reads as concealment — the hotfix rules apply unchanged.

The air-gapped reader

Some of your most valuable installs can’t reach your website at all — defense, banking, industrial networks, ships. For them, release notes that live only on a web page might as well not exist. Two rules cover it:

Notes travel with the artifact. The offline bundle — tarball, OVA, container image set — includes the release notes and the recent changelog as plain files, generated at build time by the same pipeline that builds the release. The operator in the sealed room reads the notes from the medium they’re installing from, and they read the same text as everyone else — a machine-readable copy alongside lets their tooling check versions without network access.

The canonical page stays public. Don’t lock release notes behind a customer portal. Evaluators read them before buying (an unbroken, specific changelog is your best pre-sales document), security teams read them during procurement, and search engines can’t index a login wall. The portal copy, if contracts require one, mirrors the public page — never the only copy.

Anti-patterns

  • The SaaS stream for an installable product. Undated “New this week!” posts for software people install by version number. Operators need to map every note to a version they can hold; a dateline stream is the right shape only when you run the software yourself.
  • “Just upgrade to the latest.” Notes that assume everyone is on N−1. Your median reader is on N−4 planning a jump; without a stated path they’re guessing about supported routes with production data.
  • The support-matrix ambush. Dropping PostgreSQL 13 or RHEL 8 in a patch release, mentioned in a bullet. That’s a breaking change for the people it hits — deprecation window, loud entry, or don’t do it.
  • The unsized migration. “This release includes a database migration” with no duration, lock behavior, or reversibility. The operator discovers mid-window that their table is the big one.
  • EOL announced at EOL. The branch’s final release announcing the branch is over. The people who most needed the warning — the ones who chose LTS to avoid surprises — got the least.
  • The portal-locked changelog. Notes only behind a login. Air-gapped customers can’t read them, prospects evaluating you can’t either, and the copy that should be earning search traffic is invisible.

Running an on-prem changelog on Wakelog

The shape maps on cleanly. The hosted page is the public canonical record — permalinks give change tickets and CAB requests a stable URL to cite, and the security and breaking tags make “?tag=security” your public fixed-version record for scanners and questionnaires. Operators who sit between upgrades subscribe to the RSS or JSON feed to see what’s accumulating. For branches, the project-per-branch pattern (acme-9x, acme-82-lts) keeps each stream honest, and CI posts the same entry to each branch it backports to — one curl per branch. For the air gap, the changelog.md export is built for exactly this: your release pipeline pulls it at build time and drops it into the offline bundle, so the sealed-room copy is generated from the same source as the public page. The honest caveats: Wakelog is itself hosted — air-gapped readers get the bundled export, not the live page — and no tool decides your upgrade paths or sizes your migrations. The Upgrade notes block is judgment, and it stays a writing job.

Start your changelog — free   Next: firmware release notes →

Related guides

Last updated 2026-07-29 · All guides