skip to content

You push a new build of a package to your internal RPM repository, but on a RHEL 9 client `dnf install` still offers yesterday's version. What are the likely causes, and how do you make dnf see the new build?

level: juniorimportance: should knowfreq 52%

answer

  1. dnf did not phone home
  2. metadata has a lifetime
  3. cached under /var/cache/dnf
  4. --refresh, or clean metadata
  5. check the repo index was regenerated

basics

~20 s

dnf works from cached repository metadata rather than contacting the repo on every command, so the client is usually reading a stale index. Force a refresh with dnf --refresh install or dnf clean metadata, and confirm the repo is enabled and its metadata was regenerated.

solid answer

~40 s

Three suspects, in order. First, the client cache: dnf keeps repository metadata under `/var/cache/dnf` and only re-fetches it once `metadata_expire` passes, so a fresh package can be invisible for hours. `dnf --refresh install myapp` expires the cache for that run, and `dnf clean metadata` drops it outright; `--setopt=<repoid>.metadata_expire=0` targets a single repository. Second, the repository may not be enabled at all — `dnf repolist --all` shows every configured repo and its state, since a `.repo` file under `/etc/yum.repos.d/` with `enabled=0` is skipped unless you pass `--enablerepo=<repoid>`. Third, and this catches people constantly, the server side: if nobody re-ran `createrepo_c` after copying the RPM in, the published index genuinely still lists the old version, and no amount of clearing the client cache will help.

code

bash · 12 lines
bash
# Is the repo configured, and is it enabled?
dnf repolist --all

# Ask that repo directly what it is offering
dnf repoquery --repoid=internal-apps myapp

# Force fresh metadata for this run
sudo dnf --refresh install myapp

# Or expire the cache, then install from a disabled repo once
sudo dnf clean metadata
sudo dnf install --enablerepo=internal-apps myapp

go deeper

for a junior

Be able to say that dnf caches repository metadata and that dnf --refresh or dnf clean metadata makes it fetch again, and know that .repo files live in /etc/yum.repos.d/.

for a middle

Explain the mechanics: where the cache lives, that metadata_expire governs its lifetime, and what --enablerepo, --disablerepo and --setopt change for a single invocation.

for a senior

Demonstrate the diagnostic order — server-side index timestamp, then repository state on the client, then the cache — and mention caches in front of the repository as a cause you have actually hit.

for a principal

Own the distribution design: how metadata expiry, mirror or CDN caching and repository snapshotting interact, and what convergence delay you are willing to promise a fleet after publishing a package.

## dnf does not phone home on every command A package manager that fetched a full repository index for every `install` would be unusable, so dnf downloads each repository's metadata once and caches it under `/var/cache/dnf/`, in a directory named for the repository ID and architecture. Subsequent commands read that cache. The cache is only considered stale after `metadata_expire` has elapsed — 48 hours by default in `/etc/dnf/dnf.conf`, though individual `.repo` files routinely override it with something much shorter for fast-moving repositories. That design is why "I published it, the client can't see it" is a routine support ticket rather than a real fault. The client is behaving exactly as configured. ## Forcing the client to look again ```bash sudo dnf --refresh upgrade # expire all repo metadata for this run sudo dnf clean metadata # drop the cached indexes sudo dnf makecache # fetch them again now sudo dnf --setopt=internal-apps.metadata_expire=0 install myapp ``` `--refresh` is the everyday answer: it marks the metadata expired for that invocation, so dnf re-downloads before resolving. `dnf clean metadata` removes the cached indexes and leaves downloaded packages alone; `dnf clean packages` does the reverse; `dnf clean all` does both and is the sledgehammer people reach for out of habit. `dnf makecache` prefetches metadata without installing anything — on RHEL 9 the `dnf-makecache.timer` unit runs it periodically so that interactive commands feel fast, which is another reason the cache is often warm with slightly old content. `--setopt` deserves a mention beyond this problem: it overrides any dnf or per-repository configuration option for one run, written either as a global option (`--setopt=install_weak_deps=False`) or scoped to a repository as `<repoid>.<option>`. It is how you test a configuration change before you commit it to a file. ## Is the repository even enabled? Repositories are defined by `.repo` files in `/etc/yum.repos.d/`. A definition carries an ID in square brackets, a `name`, a location (`baseurl`, or `metalink`/`mirrorlist` for mirrored content), `enabled`, and the signature-checking settings `gpgcheck` and `gpgkey`. `dnf repolist` lists the enabled repositories; `dnf repolist --all` lists every configured one with its enabled/disabled state, which is the command that ends the argument. A repository with `enabled=0` is simply not consulted — its packages are invisible to search, install and upgrade — unless you turn it on for a single run with `--enablerepo=<repoid>` (and its mirror image, `--disablerepo=<repoid>`, temporarily takes one out). Note that `enabled` and `gpgcheck` are unrelated knobs: disabling a repository is not the same as skipping its signature verification, and confusing the two is a common stumble. Once you have a candidate, `dnf repoquery --repoid=internal-apps myapp` asks that specific repository what versions it is offering, which separates "the client cannot see it" from "the repository is not serving it". ## The server side An RPM repository is a directory of packages plus a `repodata/` index generated by `createrepo_c`. Copying a new RPM into the directory changes nothing that a client can observe: until `createrepo_c --update` regenerates the index, the published metadata still describes yesterday's package set. If the metadata timestamp on the server has not moved, stop debugging the client. The same reasoning applies to caching layers in between — a CDN or reverse proxy in front of the repository can serve a stale `repomd.xml` long after the origin was updated, and the symptom is identical. ## The order to work in Check the server's `repodata` timestamp, then the client's repository state with `dnf repolist --all`, then bust the client cache with `--refresh`. Doing it in that order means you find the cause rather than making the symptom disappear; `dnf clean all` as a first move often "fixes" it by accident and teaches you nothing about why it happened, which is the answer an interviewer is quietly listening for.

  • What is the difference between `dnf clean metadata`, `dnf clean packages` and `dnf clean all`?
    `clean metadata` deletes the cached repository indexes, so the next command re-downloads them. `clean packages` deletes downloaded RPM files but keeps the indexes. `clean all` does both, plus other cached data. For a "the client cannot see my new build" problem, `clean metadata` is the targeted tool; `clean all` also throws away packages you may have to download again.
  • What does `dnf makecache` do, and why is there a systemd timer for it?
    It downloads and builds the local metadata cache without installing anything. RHEL 9 ships `dnf-makecache.timer`, which runs it periodically so interactive dnf commands do not stall on a metadata download. The side effect is that the cache is usually warm but slightly behind the repository, which is exactly the situation that makes a freshly published package appear missing.

saying these in an interview costs you the question

  • Assuming dnf contacts the repository on every command
  • Reaching for `dnf clean all` before diagnosing anything
  • Never checking whether the repository is enabled
  • Confusing enabled=0 with gpgcheck=0
  • Blaming the client when createrepo was never re-run

context