Skip to content

DNF Package Manager — Primer

Why This Matters

DNF is the default package manager on RHEL 8+, Fedora, CentOS Stream, Rocky Linux, and AlmaLinux. If you manage Red Hat-family servers in production, every package install, security patch, and compliance audit goes through dnf. Understanding its transaction model, module streams, and repo management separates someone who can dnf install nginx from someone who can manage package state across a fleet of 500 servers without surprises.

The existing package-management topic covers both apt and yum/dnf at a high level. This topic goes deep on dnf-specific features: module streams, transaction history, versionlock, dnf automatic, security-only updates, offline repos, and fleet patterns.

Core Concepts

1. DNF vs Yum — What Actually Changed

Name origin: DNF stands for Dandified Yum — literally "a fancier version of Yum." Yum itself stands for Yellowdog Updater, Modified — originally written for the Yellow Dog Linux distribution (a PowerPC Linux distro, 1999). DNF was created by Red Hat engineers starting in 2012 as a ground-up rewrite using libsolv (SUSE's dependency solver, written in C) instead of Yum's slow Python-based solver. DNF became the default on Fedora 22 (2015) and RHEL 8 (2019).

On RHEL 8+, yum is a symlink to dnf. Yum4 is dnf under the hood. The key differences from legacy yum (yum3, RHEL 7):

Feature Yum3 (RHEL 7) DNF / Yum4 (RHEL 8+)
Dependency solver Custom (slow, sometimes wrong) libsolv (fast, correct)
Module streams Not available Full support
Performance Slow metadata parsing C-based hawkey, much faster
Python API yum module dnf module (incompatible)
Config compat N/A Reads yum.conf, yum.repos.d
Plugin API yum plugins dnf plugins (different API)

If you have scripts that call yum, they still work on RHEL 8+ — but you should understand you are running dnf. Old yum3 plugins will not work; they need dnf equivalents.

2. Repository Configuration

Repo files live in /etc/yum.repos.d/*.repo. Each file can define one or more [repo-id] sections.

[company-internal]
name=Company Internal Packages
baseurl=https://rpm.internal.company.com/el9/$basearch/
enabled=1
gpgcheck=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-company
priority=10
sslverify=1
sslcacert=/etc/pki/tls/certs/company-ca.pem

Key fields:

  • baseurl / metalink / mirrorlist: Where to fetch packages. Use $releasever and $basearch variables for portability.
  • gpgcheck=1: Always enable in production. Unsigned packages are a supply chain risk.

Gotcha: If you add a third-party repo and forget to import its GPG key, every dnf install from that repo will fail with a "Public key not installed" error. Import keys before enabling repos: rpm --import https://repo.example.com/RPM-GPG-KEY-example. And never set gpgcheck=0 in production — it silently disables signature verification, opening the door to tampered packages. - gpgkey: Path or URL to the GPG public key for signature verification. - priority: Requires dnf-plugin-priorities (or built-in on some distros). Lower number = higher priority. Use to ensure internal repos override EPEL. - cost: Higher cost = less preferred. Default is 1000. Useful for slow mirrors. - enabled: 0 or 1. You can also toggle at runtime with --enablerepo / --disablerepo. - exclude: Space-separated list of package globs to never install from this repo. - module_hotfixes=1: Allow this repo to override module-stream-filtered packages (needed for some third-party repos).

List all configured repos and their status:

dnf repolist --all

3. Module Streams and Profiles

Module streams solve the "which version of PostgreSQL?" problem. A single OS release can offer multiple versions of the same software simultaneously.

# List available modules
dnf module list

# List streams for a specific module
dnf module list postgresql

# Enable a stream (sticky — see footguns)
dnf module enable postgresql:15

# Install the default profile from the enabled stream
dnf module install postgresql:15

# Install a specific profile
dnf module install postgresql:15/server

# Check what's enabled
dnf module list --enabled

Key concepts:

  • Module: A set of packages that belong together (e.g., postgresql).
  • Stream: A version track (e.g., 15, 16). Only one stream can be active per module.
  • Profile: A preset package list within a stream (e.g., server, client, devel).

Streams filter what packages are visible. When you enable postgresql:15, packages from the :16 stream become invisible to the resolver. This prevents accidental version mixing.

Default trap: Once you enable a module stream, it is sticky. Switching streams (e.g., postgresql:15 to postgresql:16) requires dnf module reset postgresql followed by dnf module enable postgresql:16. A direct dnf module enable postgresql:16 without reset will fail with a confusing "module stream is already enabled" error. This catches people during major version upgrades.

Remember: Module stream workflow: "E-I-R-E" — Enable the stream, Install the profile, Reset when switching, Enable the new stream. Always dnf module list --enabled to check what is active before making changes.

4. Transaction Model and History

Every dnf operation that changes packages is a transaction. DNF records every transaction in an SQLite database at /var/lib/dnf/history.sqlite (RHEL 8) or /var/lib/dnf/history/ (RHEL 9+).

# List recent transactions
dnf history list

# Show details of a specific transaction
dnf history info 42

# Undo a single transaction (reverse its changes)
dnf history undo 42

# Rollback to the state before transaction 42
dnf history rollback 42

# Redo a previously undone transaction
dnf history redo 42

undo vs rollback:

  • undo 42 reverses only transaction 42's changes.
  • rollback 42 reverses all transactions after 42, restoring the system to the state it was in right after transaction 42 completed.

This is the closest thing to a "package state snapshot" that RPM-based systems offer natively.

War story: A common fleet incident: someone runs dnf update on a production server and a new package version breaks the application. With dnf history undo last, you can reverse the exact set of packages that were changed — rolling back to the previous working state without a full OS reinstall. This is why experienced ops engineers always check dnf history list before and after maintenance windows.

5. Groups and Environments

Groups bundle related packages. Environments bundle groups.

# List available groups
dnf group list
dnf group list --hidden   # includes hidden groups

# See what's in a group
dnf group info "Development Tools"

# Install a group
dnf group install "Development Tools"

# Install only mandatory + default packages (skip optional)
dnf group install --setopt=group_package_types=mandatory,default "Development Tools"

# Remove a group (removes packages unique to that group)
dnf group remove "Development Tools"

Groups track membership. If you install a group and a new package is later added to it, dnf group upgrade will pick it up.

6. DNF Variables and Conditional Config

DNF supports variables in repo configs and the main config:

Variable Source Example value
$releasever OS release 9
$basearch Architecture x86_64
$arch Architecture (finer) x86_64
Custom vars Files in /etc/dnf/vars/ Whatever you define

Custom variables are useful for fleet management:

# Create a custom variable
echo "production" > /etc/dnf/vars/environment

# Use in repo config
# baseurl=https://rpm.internal.company.com/$environment/el$releasever/$basearch/

This lets you point dev, staging, and production servers at different repo paths using the same repo file, just by changing the variable file.

Quick Reference

# Search for packages
dnf search nginx
dnf provides '*/nginx.conf'      # which package owns a file?

# Install / remove
dnf install nginx
dnf remove nginx
dnf reinstall nginx

# Update
dnf check-update                  # list available updates (exit 100 if updates exist)
dnf update                        # apply all updates
dnf update nginx                  # update specific package
dnf update --security             # security patches only

# Info and queries
dnf info nginx                    # package details
dnf list installed                # all installed packages
dnf repoquery --requires nginx    # what does nginx need?
dnf repoquery --whatprovides '/etc/nginx/nginx.conf'

# History
dnf history list
dnf history undo last

# Modules
dnf module list
dnf module enable postgresql:15
dnf module install postgresql:15/server

# Cache
dnf clean all                     # wipe cached metadata + packages
dnf makecache                     # rebuild metadata cache

# Config inspection
dnf config-manager --dump         # show effective config

Wiki Navigation

Prerequisites

  • DNF Flashcards (CLI) (flashcard_deck, L1) — DNF Package Manager