Fedora

dnf versionlock on Fedora: Lock Packages and Kernels (dnf5)

Older guides tell you to install python3-dnf-plugin-versionlock and edit /etc/dnf/plugins/versionlock.list. On current Fedora that file does nothing at all.

Original content from computingforgeeks.com - post 176780

dnf5 ships versionlock as a built-in command with its own TOML file, and the old list sits there ignored while dnf upgrades the package you thought you had pinned. The new format is also more capable than the old one, because a lock can be a version range or a name glob instead of one exact build. This guide locks a real package, shows what the lock does and doesn’t hide, keeps a Kubernetes minor stream with a range, and ends with the kernel case where a lock on the obvious package does not hold the kernel back. All of it was run on Fedora 45 with dnf5 5.4.6 in October 2026, against the upstream Kubernetes repository and Fedora’s own kernel packages.

Versionlock is built into dnf5

You do not install anything to get the command on current Fedora. It’s part of the core dnf5 and libdnf5 packages, which ship the man pages and own the lock file, and the plugin directory that older releases used is empty. Ask rpm which files belong to the core packages:

rpm -ql dnf5 libdnf5 | grep -i versionlock

Both man pages and the configuration path come from those two packages, so no separate plugin is involved:

/usr/share/man/man8/dnf-versionlock.8.gz
/usr/share/man/man8/dnf5-versionlock.8.gz
/etc/dnf/versionlock.toml

The lock file doesn’t exist until you add the first lock, so a missing /etc/dnf/versionlock.toml just means nothing is pinned. The command has five subcommands, in the order you’ll usually need them:

  1. add pins the installed version of a package
  2. list shows every entry with its conditions
  3. exclude blocks one specific version and allows the rest
  4. delete removes the entries for a package
  5. clear removes every entry

What happens to a lock list left behind by dnf4? It’s ignored. With the TOML file empty, a line in /etc/dnf/plugins/versionlock.list pinning kubectl to its installed version changed nothing, and dnf check-upgrade still offered the newer build:

Upgrades
kubectl.x86_64 1.37.1-150500.1.1 kubernetes

If a system was upgraded from a dnf4 release with locks in place, those locks are gone in practice even though the file is still on disk. Re-create them with dnf versionlock add before the next dnf upgrade, then delete the old list so nobody trusts it later. The DNF5 cheat sheet lists the other commands whose behaviour changed in the same move.

A lock pins the installed version

A package needs more than one version in its repositories before a lock means anything, and Fedora’s own repos often carry only the latest build. The upstream Kubernetes repository keeps every patch release in a minor stream, which makes it a realistic test and the most common reason people pin packages at all. Open a repo file for it:

sudo nano /etc/yum.repos.d/kubernetes.repo

Add the repository definition. This is the exact file the lab used, for one Kubernetes minor:

[kubernetes]
name=Kubernetes
baseurl=https://pkgs.k8s.io/core:/stable:/v1.37/rpm/
enabled=1
gpgcheck=1
gpgkey=https://pkgs.k8s.io/core:/stable:/v1.37/rpm/repodata/repomd.xml.key

The official kubeadm instructions add one more line, exclude=kubelet kubeadm kubectl cri-tools kubernetes-cni, and install with --setopt=disable_excludes=kubernetes. That line was left out here on purpose, because it hides every update from those packages whether or not a lock exists. If your repo file has it, add the same --setopt to each install and upgrade command below or dnf will answer Nothing to do.

Install the older patch release on purpose, so an upgrade is waiting. dnf imports the repository key on the first transaction:

sudo dnf install kubelet-1.37.0 kubeadm-1.37.0 kubectl-1.37.0

Lock all three with one command. Without a version in the spec, add takes the installed build:

sudo dnf versionlock add kubelet kubeadm kubectl

Compare the upgrade that was waiting before the lock with what dnf reports after it:

dnf versionlock add pinning kubelet kubeadm and kubectl on Fedora 45 so check-upgrade and dnf upgrade report nothing

Each package got an exact evr condition, and the same three upgrades that check-upgrade listed a moment earlier disappeared. The lock lives in plain TOML, so you can read it and keep it in configuration management:

cat /etc/dnf/versionlock.toml

Every entry carries a name, a comment with the time it was added, and one or more conditions. The first of the three looks like this:

version = "1.0"

[[packages]]
name = "kubelet"
comment = "Added by 'versionlock add' command on 2026-10-22 10:18:28"

[[packages.conditions]]
key = "evr"
comparator = "="
value = "1.37.0-150500.1.1"

A spec that names a package you haven’t installed behaves differently. The dnf5 versionlock documentation says add searches installed packages first and falls back to the available ones, so without a version in the spec it pins whatever the repositories offer today, the newest build included. Give it a full version, such as kubectl-1.37.0, if you want to pin before the first install.

Locked packages vanish without a word

dnf doesn’t tell you it skipped anything. A locked package simply drops out of the transaction, so dnf upgrade prints Nothing to do. on a machine that’s several patch releases behind, and an unattended job never warns you. Five commands behave differently, and you need to know which one tells the truth:

  • dnf check-upgrade and dnf upgrade apply the lock and show nothing
  • dnf install of a locked-out version fails with an error
  • dnf list --upgrades ignores the lock and still lists the newer build
  • dnf repoquery --upgrades applied the lock in the lab and listed nothing

Here are the error and the list output, captured with all three packages still locked:

dnf install failing with matches only packages excluded by versionlock while dnf list --upgrades still shows the locked updates

That list output isn’t a bug. The man page says versionlock “does not apply any excludes in non-transactional operations like repoquery, list, info”, so dnf list --upgrades answers what the repositories offer, not what dnf will install. The repoquery command did not match that sentence on this dnf5 build, so you should not rely on it either way. Use dnf list --upgrades on purpose as your reminder of what you are holding back, and use check-upgrade when you want the answer dnf will act on.

Argument matches only packages excluded by versionlock

You get this error when a lock blocks the exact version you asked for:

Failed to resolve the transaction:
Argument 'kubectl-1.37.1' matches only packages excluded by versionlock.

The message means the version exists and the lock rejected it. Run dnf versionlock list to find the entry, and if the version you want is the one you meant to allow, the lock is wrong rather than the repository. A plain sudo dnf upgrade kubectl doesn’t even get that far, because with the newer build filtered out it answers Nothing to do. and exits 0.

Planned upgrades go through the lock file

dnf5 has one switch that skips versionlock for a single command, because the lock is implemented as package excludes. --setopt=disable_excludes="*" turns off every exclude, versionlock included, and with the kubectl lock in place a dry run offered the newer build:

sudo dnf upgrade --assumeno --setopt=disable_excludes="*" kubectl

The transaction table listed the upgrade the lock had been hiding:

Upgrading:
 kubectl             x86_64 0:1.37.1-150500.1.1 kubernetes  59.0 MiB

That also drops excludepkgs from dnf.conf and every repo for that run, so treat it as a sledgehammer you use rarely. The repo-scoped form from the kubeadm docs, --setopt=disable_excludes=kubernetes, does not bypass versionlock at all: it still answered Nothing to do. The cleaner way to move a pinned package takes three steps, in order:

  1. delete the package’s entry
  2. upgrade only that package
  3. add the lock again on the new version

Start by removing the entry.

sudo dnf versionlock delete kubectl

delete prints the entry it removed, so you can see exactly which condition was holding the package:

Deleting versionlock entry:
# Added by 'versionlock add' command on 2026-10-22 10:21:18
Package name: kubectl
evr = 1.37.0-150500.1.1

Upgrade only that package, so nothing else moves in the same transaction:

sudo dnf upgrade kubectl

Then pin it again. add reads the installed build, which is now the new one:

sudo dnf versionlock add kubectl

The new entry records the version you just moved to:

Adding versionlock on "kubectl = 1.37.1-150500.1.1".

For kubeadm clusters this is the order the upgrade procedure expects anyway, one package at a time, so script it per node rather than with a blanket dnf upgrade.

Exclude one bad release instead

Sometimes you want every update except one known-bad build. exclude writes a != condition, so the package keeps updating normally and skips just that version. Never mix it with an add entry for the same name, though, because entries are combined with OR. With kubectl pinned at = 1.37.0 and an exclude for that same build added on top, check-upgrade offered the newer kubectl again, since one entry or the other now matched every version. Remove the exact lock first:

sudo dnf versionlock delete kubectl

Then add the exclude, naming the build with its architecture. The captures in this section come from a node still on the older patch release, so the build being skipped is the newer one; if you followed the upgrade above, you’re already on it, so name the next build you want to skip instead:

sudo dnf versionlock exclude kubectl-1.37.1-150500.1.1.x86_64

Why the architecture? The Kubernetes repository publishes four binary architectures under one version. Without the suffix, dnf versionlock exclude kubectl-1.37.1 added the same condition four times, one per binary architecture:

Adding versionlock exclude on "kubectl = 1.37.1-150500.1.1".
Adding versionlock exclude on "kubectl = 1.37.1-150500.1.1".
Adding versionlock exclude on "kubectl = 1.37.1-150500.1.1".
Adding versionlock exclude on "kubectl = 1.37.1-150500.1.1".

Those duplicates are harmless, because every condition says the same thing, but they make the file harder to read and review. With the arch suffix you get a single entry, check-upgrade stays quiet until a later build appears, and an explicit install of that build fails with the same “excluded by versionlock” error as a full lock.

Ranges keep a minor stream

An exact pin is too strict for most servers, because it also blocks the security patches inside the release you are on. The TOML format gives each entry three parts, and every condition in an entry must match, so two conditions make a range:

  • name, an exact package name or a shell glob
  • comment, the label that list prints
  • conditions on evr, epoch or arch, compared with =, !=, <, <=, > or >=

add can’t write a range, so you edit the file yourself, and dnf reads it on the next transaction because there’s no daemon to reload. Start from an empty lock list.

sudo dnf versionlock clear

Then open the file in your editor, since this lock type has no command of its own.

sudo nano /etc/dnf/versionlock.toml

Add one entry that holds every package whose name starts with kube on its current minor while letting patch releases through. It is the file the lab installed:

version = "1.0"

# kubelet, kubeadm and kubectl stay on the 1.37 minor; patch releases still install
[[packages]]
name = "kube*"
comment = "Kubernetes 1.37 patch releases only"
[[packages.conditions]]
key = "evr"
comparator = ">="
value = "1.37"
[[packages.conditions]]
key = "evr"
comparator = "<"
value = "1.38"

dnf versionlock list reads it back in a shorter form, and the comment field becomes the heading of the entry:

# Kubernetes 1.37 patch releases only
Package name: kube*
evr >= 1.37
evr < 1.38

Both directions held in the lab. sudo dnf upgrade moved all three packages to the newer patch release, because that’s inside the range. With the previous minor’s repository added next to it, installing kubectl-1.36.5 failed with “matches only packages excluded by versionlock”, because it’s below the lower bound. The same shape works for drivers: the man page’s own example keeps every *nvidia* package on one driver branch, which is useful with the RPM Fusion NVIDIA drivers when a new branch drops your GPU.

Check what your glob catches before you rely on it. The same repository ships kubernetes-cni, whose versions are far below the range, so kube* blocks every build of it. The lab did not notice because Fedora’s containernetworking-plugins covered kubelet’s dependency, but on a node that takes kubernetes-cni from the Kubernetes repo, write one entry per package name instead.

Keep your notes in the comment field, not in # lines. Every add, exclude or delete rewrites the whole file from its parsed contents, so a hand-written # line disappeared after the next dnf versionlock delete, while the comment field survived. That matters if the file lives in Ansible or Git, because the next dnf change will show up as a diff that drops your explanations.

Locking kernel-core does not hold the kernel

A lock on the obvious package fails for the kernel, because a Fedora kernel is split across packages that depend on each other through a virtual name. On the lab VM, one kernel version meant up to three packages:

  • kernel-core, the kernel image itself
  • kernel-modules-core, the essential kernel modules
  • kernel-uki-virt, a unified kernel image for virtual machines

The modules package doesn’t require kernel-core by name. It requires a virtual capability, kernel-uname-r, at its own version:

rpm -q --requires kernel-modules-core | grep kernel-uname-r

Each installed kernel prints one line, and the line for the newest one in the lab read:

kernel-uname-r = 7.2.11-300.fc45.x86_64

So which packages can satisfy it? Ask the repositories:

dnf repoquery --whatprovides kernel-uname-r --latest-limit=1

Four packages provide the capability, and only one of them is kernel-core:

kernel-core-0:7.2.11-300.fc45.x86_64
kernel-debug-core-0:7.2.11-300.fc45.x86_64
kernel-debug-uki-virt-0:7.2.11-300.fc45.x86_64
kernel-uki-virt-0:7.2.11-300.fc45.x86_64

So what does dnf do when you lock kernel-core to the installed versions and a newer kernel is in the repository? The lab VM had the newest kernel removed so the update was pending, then ran sudo dnf versionlock add kernel-core and a normal upgrade. dnf installed the new modules package and satisfied its dependency with kernel-uki-virt instead:

Fedora 45 dnf upgrade installing kernel-uki-virt and filling the EFI partition after only kernel-core was version locked

Your lock held the package it named, and the kernel moved anyway. On this VM the side effect was worse than an unwanted kernel, because the UKI copy filled the 100 MB EFI partition and the scriptlet failed, while dnf still finished with Complete!. The history shows kernel-uki-virt and its helpers marked as dependencies:

  Install  kernel-modules-core-0:7.2.11-300.fc45.x86_64 Dependency      fedora
  Install  kernel-uki-virt-0:7.2.11-300.fc45.x86_64     Dependency      fedora
  Install  uki-direct-0:26.9-1.fc45.noarch              Weak Dependency fedora

sudo dnf history undo last removed the whole transaction and freed the partition again. Locking kernel-uki-virt with add does not help, because for a package that isn’t installed it pins the available build, and in the lab that was the new kernel itself: Adding versionlock on "kernel-uki-virt = 7.2.11-300.fc45". The lock that works is a name glob with an upper bound. Find the version you want to stay on:

rpm -q --qf "%{EVR}\n" kernel-core

The lab VM had two kernels left, and the running one was the newer of the two:

7.2.0-61.fc45
7.2.7-300.fc45

Replace the file contents with one entry that uses that value:

version = "1.0"

[[packages]]
name = "kernel*"
comment = "Stay on the 7.2.7 kernel or older"
[[packages.conditions]]
key = "evr"
comparator = "<="
value = "7.2.7-300.fc45"

With that entry, check-upgrade showed no kernel packages, a dry run of dnf upgrade offered only the unrelated kubectl update, and an explicit install was refused:

Failed to resolve the transaction:
Argument 'kernel-uki-virt' matches only packages excluded by versionlock.

A kernel* glob also catches kernel-devel, kernel-headers and kernel-tools, which is usually what you want, since akmods and DKMS build out-of-tree modules against a kernel-devel that matches the running kernel. Once you lift the lock, dnf goes back to trimming old kernels past installonly_limit, and the older kernel guide shows how to keep a known-good one around. If the reason for the lock is a boot problem rather than a regression, the GRUB configuration guide shows how to make the older entry the default instead.

Which lock to use for which job

Each lock type answers a different question, so pick by what you want to keep moving rather than by habit:

GoalHowEntry writtenUpdates that still install
Freeze one package exactlydnf versionlock add NAMEevr = installedNone
Skip one known-bad builddnf versionlock exclude NAME-VER-REL.ARCHevr != that buildEvery other version
Stay on a minor or driver branchHand-written entry in /etc/dnf/versionlock.toml>= and < pairPatch releases inside the range
Hold the kernelHand-written kernel* globevr <= currentNone for any kernel package
Move a pinned package on purposedelete, upgrade NAME, addNew exact entryThe one you upgraded
Ignore every lock for one command--setopt=disable_excludes="*"NothingAll, and other excludes too
Drop every lockdnf versionlock clearEmpty listAll

Whatever you lock, check it with dnf check-upgrade after the change rather than dnf list --upgrades, since only the first applies the lock. If the packages come from a third-party repository, the COPR repositories guide shows how to see which repository a build came from before you pin it, and the Fedora 45 post-install checklist covers the rest of a new install.

Keep reading

Enable RPM Fusion and Install Multimedia Codecs on Fedora 45 / 44 / 43 Fedora Enable RPM Fusion and Install Multimedia Codecs on Fedora 45 / 44 / 43 Harden Fedora 45 / 44 for Desktop and Server: Complete Guide Fedora Harden Fedora 45 / 44 for Desktop and Server: Complete Guide Things to Do After Installing Fedora 44 Workstation Fedora Things to Do After Installing Fedora 44 Workstation Fix ptrace Operation not permitted for gdb and strace on Fedora Fedora Fix ptrace Operation not permitted for gdb and strace on Fedora Fedora 45 vs Bazzite for Gaming: Steam, Updates and NVIDIA Fedora Fedora 45 vs Bazzite for Gaming: Steam, Updates and NVIDIA Install Apache Solr on CentOS / Fedora / Rocky Linux CentOS Install Apache Solr on CentOS / Fedora / Rocky Linux

Leave a Comment

Press ESC to close