An old runbook written for RHEL 7 tells you to run `yum remove <pkg>` and then `yum update -y` on RHEL 8 servers, where yum is now dnf. Which of those invocations can behave differently than they did under the original yum, and why?
answer
- the name survived, the defaults did not
- removal is no longer a single package
- strictness moved into the shipped config
- two escape hatches that mean different things
- read the transaction summary before -y
basics
~20 sBoth can. RHEL 8 sets clean_requirements_on_remove=True, so a remove also takes now-unneeded dependencies with it, and best=True, so an upgrade that cannot reach the newest version fails the whole transaction instead of quietly skipping the package.
solid answer
~50 sThe command names survived the move to dnf, but the shipped defaults did not. RHEL 8's `/etc/dnf/dnf.conf` sets `clean_requirements_on_remove=True`, so `yum remove` now also removes dependencies that were pulled in automatically and are no longer required by anything — a removal that took one package on RHEL 7 can take twenty. It also sets `best=True` and `skip_if_unavailable=False`, so an upgrade that cannot install the newest available version of some package, or that hits a repository it cannot refresh, fails the transaction with a depsolve error rather than upgrading everything else and moving on. `--nobest` relaxes the first behaviour and `--skip-broken` drops the offending packages instead. A third difference is that `update` and `upgrade` are now the same command, with obsoletes processing on. Always run the runbook's commands once with `--assumeno` or read the transaction summary before letting `-y` answer for you.
code
bash · 4 linesdnf remove --assumeno httpd
dnf remove --setopt=clean_requirements_on_remove=False httpd
dnf upgrade --nobest
dnf upgrade --skip-brokengo deeper
Know that on RHEL 8 the yum command runs dnf, and that you should read the transaction summary a removal or upgrade prints before confirming it rather than passing -y straight away.
Explain the two shipped defaults that change outcomes: clean_requirements_on_remove pulling unused dependencies out with a removal, and best making an unattainable upgrade an error instead of a skip.
Show the operational judgment — canary the runbook on one host, choose --nobest versus --skip-broken deliberately, and treat a new depsolve failure as a signal about your repository set rather than something to flag away.
Own the fleet-wide question: how you audit a decade of inherited automation for commands whose semantics moved under a preserved name, and what your own deprecation policy should promise about behaviour as opposed to interface.
## Why identical commands are not identical operations On RHEL 8 and 9 the `yum` command is a symlink to dnf, so an old runbook keeps executing. What changed underneath is the depsolver and, just as importantly, the configuration Red Hat ships in `/etc/dnf/dnf.conf`. RHEL's shipped main section pins a handful of options — notably `clean_requirements_on_remove=True`, `best=True` and `skip_if_unavailable=False` — that make dnf stricter and more thorough than yum 3 was out of the box. The commands did not change; the policy did. ## `yum remove` now cleans up after itself Under dnf, `clean_requirements_on_remove` defaults to `True` and RHEL sets it explicitly. When you remove a package, dnf also removes any package that was originally installed only as a dependency and that nothing else now requires. yum 3 did not do this by default, so a removal there took the named package and its dependants and stopped. This is usually what you want on a workstation and occasionally catastrophic on a server, because "installed as a dependency" is a recorded fact about *how* a package arrived, not about how important it is. A library that arrived years ago as a dependency of something you are now removing, but which a hand-compiled application in `/opt` links against, has no RPM-level requirer — so dnf takes it. The RPM database cannot see software that was not installed as a package. The defences are simple and worth building into any runbook: ```bash dnf remove --assumeno <pkg> # print the transaction, change nothing dnf remove --setopt=clean_requirements_on_remove=False <pkg> ``` The first is the habit that matters: read the removal list before `-y` reads it for you. ## `yum update` can now fail where it used to shrug `best=True` tells dnf that if the newest version of a package in the enabled repositories cannot be installed — because of a conflict, a missing dependency, or a partially mirrored repository — the transaction is an error rather than something to work around by installing an older version or skipping the package. yum 3's behaviour felt more forgiving: it would frequently leave the problem package alone and upgrade the rest. The consequence for automation is that an upgrade step which "always worked" starts exiting non-zero, and the honest response is usually to read the depsolve error rather than to suppress it, because it is telling you something real about your repository set. When you do need to proceed, the two escape hatches mean different things: - `--nobest` — allow dnf to install the best *installable* version rather than insisting on the newest. - `--skip-broken` — an alias for `--setopt=strict=0`; remove the packages that cause the depsolve problem from the transaction entirely, upgrading everything else. Picking `--skip-broken` when you meant `--nobest` silently drops packages from a security upgrade, which is precisely the kind of thing that shows up later as an unpatched CVE. `skip_if_unavailable=False` is the related repository-level rule: a repository that will not refresh — a dead mirror, an expired proxy credential, a subscription that lapsed — fails the whole transaction instead of being quietly ignored. Again this is a better default, and again it turns a formerly silent partial upgrade into a visible failure. ## `update` and `upgrade` converged In yum 3, `update` and `upgrade` were subtly different: `upgrade` was `update` with obsoletes processing enabled. In dnf they are the same command, and obsoletes handling is on. Any runbook that carefully wrote one rather than the other for that reason is now expressing a distinction that no longer exists — harmless, but a good marker that the document has not been touched since RHEL 7. ## Automation, not just shells The same drift shows up above the command line. Ansible's `yum` module dispatches to a dnf backend on RHEL 8+ and needs the `python3-dnf` bindings present, since yum's own Python API no longer exists; the module is deprecated in favour of `ansible.builtin.dnf`. A playbook that installed `python2-dnf` or assumed `import yum` will fail before it ever reaches a package decision. ## How to modernise an old runbook Do not translate it mechanically. Run each destructive step once, interactively, with the transaction summary in front of you: 1. `dnf remove --assumeno` for every removal, and diff the list against what the runbook claims it removes. 2. Run the upgrade without `-y` on one canary host and read any depsolve error rather than reaching for a flag. 3. Decide deliberately between `--nobest` and `--skip-broken` if you need one, and record *why* in the runbook. 4. Replace `yum` with `dnf` in the text once you have verified each step, so the next reader is not misled by a name that no longer describes what runs. The interview point behind all of this: a compatibility alias preserves the interface, never the behaviour. Treating the alias as a guarantee of identical outcomes is how a routine patch window becomes an incident.
- What is the practical difference between --nobest and --skip-broken on a failing upgrade?`--nobest` still upgrades the problem package, just to the newest version that is actually installable. `--skip-broken` is an alias for `--setopt=strict=0`: it drops the problem packages from the transaction entirely and upgrades the rest. If the dropped package is the one carrying a security fix, `--skip-broken` leaves you unpatched while reporting success.
- Why can clean_requirements_on_remove remove a library that something on the box is still using?Because it reasons only over the RPM database. It removes packages marked as installed-as-a-dependency that no other *package* requires. Anything not installed via RPM — a hand-compiled binary in /opt, a vendored application, a locally built agent — is invisible to that check, so its dependencies look unused.
- A patch window now fails with a depsolve error where the old runbook always succeeded. What is your first move?Read the error rather than add a flag. It usually names a real problem: a partially synced internal mirror, a lapsed subscription, a locally installed package conflicting with the stream, or a repository that will not refresh. Suppressing it with --skip-broken converts a visible failure into a silent gap in patching.
- An old Ansible role uses the yum module on RHEL 8 and fails before touching any package. Why?The module dispatches to a dnf backend on RHEL 8+ and needs the `python3-dnf` bindings on the managed host; yum's own Python API was removed with yum 3. Install the bindings, and move the role to `ansible.builtin.dnf`, which the yum module is deprecated in favour of.
saying these in an interview costs you the question
- Assumes yum on RHEL 8 behaves exactly as it did on RHEL 7
- Uses --skip-broken on a security upgrade without reading what was dropped
- Thinks yum remove only ever removes the named package
- Suppresses a depsolve error with a flag instead of diagnosing it
- Runs runbook removals with -y without previewing the transaction