Fedora

Manage Packages on Fedora Silverblue 45 / 44 with rpm-ostree

A Fedora Silverblue host has no dnf. The operating system is a read-only image, so you install packages in other ways: Flatpak for desktop apps, Toolbx for command line tools, rpm-ostree layering for software that must live on the host, and a bootc image when you’d rather build the whole system from a Containerfile. Each one behaves differently across reboots and major upgrades, and that’s what should decide where a package goes.

Original content from computingforgeeks.com - post 42755

We installed Silverblue 45 and 44 from the official ISOs on two KVM machines, updated both to the October 16 compose and ran every command here in October 2026. Rebasing the 44 machine to 45 with RPM Fusion layered produced the costliest failure: 1.7 GB downloaded, then a 404 from an RPM Fusion repo. The bootc sections ran on the 45 machine after switching it to the quay.io image.

Pick where each package goes

Every install method on Silverblue trades convenience against how much it changes the host. Flatpak and Toolbx never touch the OS image, so upgrades and rollbacks ignore them completely. Layering with rpm-ostree rebuilds the OS image with your packages added, which is why it needs a reboot and why a broken layered package can block an upgrade. We tested five options, and they compare like this.

MethodUse it forSurvives a rebootAcross a major upgradeReboot to use it
FlatpakDesktop apps (Firefox, LibreOffice, GIMP)YesUntouched; updates on its own channelNo
Toolbx containerCompilers, CLI tools, per-project dependenciesYesUntouched; the container keeps the release you created it withNo
rpm-ostree installHost software: shells, drivers, libvirt, VPN clientsYesRe-resolved against the new releaseYes, or --apply-live
dnf install --transientA quick test on a host booted from the bootc imageNoNoNo
Custom bootc imageA reproducible system you rebuild from a ContainerfileYesRebuild on the new base imageYes

On a desktop we’d install apps from Flathub first, put development tools in a Toolbx container, and keep layered packages to the handful that genuinely need the host, such as a shell or a kernel driver. Every layered package is one more thing rpm-ostree has to resolve on every update and every rebase, and the RPM Fusion failure later in this guide shows what happens when one of those repos isn’t ready. A custom bootc image is worth the extra work once you manage more than one machine, or once your layer list grows past a few packages. Until then, you won’t gain much from it, and you give up signed OSTree updates to get it.

Check what is on the host

Confirm that first, because it explains the rest of the page. On a Silverblue host installed from the ISO, the only dnf binary is dnf5daemon-server, pulled in as a GNOME Software dependency, so typing dnf install gets you command not found.

ls -l /usr/bin/dnf*

You’ll find no dnf or dnf5 command, only dnf5daemon-server.

-rwxr-xr-x. 1 root root 730112 Jan  1  1970 /usr/bin/dnf5daemon-server

Next, look at the deployment you are running. rpm-ostree status is the command you will read most on Silverblue, because it shows the booted image, anything staged for the next boot and every customisation layered on top:

rpm-ostree status

The booted deployment sits at the top, marked with the dot, and tracks the fedora/45/x86_64/silverblue branch on the fedora remote. After the first update you’ll also see the previous deployment listed below it as the rollback; the slice below shows only the booted entry.

State: idle
Deployments:
● fedora:fedora/45/x86_64/silverblue
                  Version: 45.20261016.n.0 (2026-10-16T07:41:13Z)
                   Commit: 26d3e5416fd143c2c3137ddaaeb5d2a024fa6db2b4e2db722462f43296eab286
             GPGSignature: Valid signature by 4F50A6114CD5C6976A7F1179655A4B02F577861E

Watch the fedora: prefix, because it matters later. It means the system pulls signed OSTree commits from Fedora’s OSTree repository, which is what every ISO install does on both releases. A system switched to the container image shows ostree-unverified-registry: there instead, and a few commands behave differently, as the bootc section explains. If you’re following this on your own machine, run rpm-ostree status after every step below: it’s the quickest way to see what changed and what is waiting for a reboot.

List installed packages with rpm-ostree

The RPM database is still there and still readable, so rpm -qa lists every package in the booted deployment, base image and layers together. It is the quickest answer when you only want to know whether something is installed:

rpm -qa | wc -l
rpm -qa | grep -E "^(podman|toolbox|firefox)-[0-9]"

Our updated 45 machine carried 1,503 packages, with Podman, Firefox and Toolbx all in the base image.

1503
podman-6.1.3-1.fc45.x86_64
firefox-157.0-1.fc45.x86_64
toolbox-0.3-7.fc45.x86_64

That count mixes the image and your own changes, so it can’t tell you what you added. To see what a specific OS commit contains without booting it, ask rpm-ostree db list about the branch or a commit hash. Adding a package name filters the list, which is handy for checking whether a newer commit still ships something you depend on.

rpm-ostree db list fedora/45/x86_64/silverblue | head -6
rpm-ostree db list fedora/45/x86_64/silverblue firefox

Each listing starts with the commit it read, then one package per line, so you always know which tree you’re looking at. The full list held 1,504 lines, the 1,503 packages plus that header.

ostree commit: fedora/45/x86_64/silverblue (26d3e5416fd143c2c3137ddaaeb5d2a024fa6db2b4e2db722462f43296eab286)
 7zip-26.03-1.fc45.x86_64
 ImageMagick-1:7.1.2.32-2.fc45.x86_64
 ImageMagick-libs-1:7.1.2.32-2.fc45.x86_64
 LibRaw-0.22.2-1.fc45.x86_64
 ModemManager-1.24.2-5.fc45.x86_64

What most people actually want is the short list of changes they made themselves, because that’s what follows the machine through every upgrade. rpm-ostree status reports those changes on three lines, and each one comes from a different command:

  • LayeredPackages: packages added with rpm-ostree install
  • RemovedBasePackages: image packages hidden with override remove
  • LocalOverrides: image packages swapped with override replace

For scripts, the JSON output carries this data under its own key names, and jq ships in the base image. The filter below covers layered packages and base removals; replacements live under base-local-replacements and RPMs installed from a file or URL under requested-local-packages, so add those keys if you use them.

rpm-ostree status --json | jq -r ".deployments[0] | {version, \"requested-packages\", packages, \"base-removals\", \"requested-base-removals\"}"

On a pristine machine all four lists are empty, which is the state you return to with rpm-ostree reset. Our machine had nothing layered yet.

{
  "version": "45.20261016.n.0",
  "requested-packages": [],
  "packages": [],
  "base-removals": [],
  "requested-base-removals": []
}

To compare two deployments, for example after an update is staged, rpm-ostree db diff with no arguments compares the booted deployment with the pending one and lists every upgraded, removed and added package. Run it before you reboot into an update you’re unsure about, because the upgrade’s own output has usually scrolled away by then. If something you rely on shows under Removed, skip that deployment with sudo rpm-ostree cleanup -p, which deletes the pending one (our test dropped the count from two deployments to one), and wait for the next compose.

Search for and layer an RPM package

Layering is for software that has to run on the host itself. rpm-ostree search queries the same Fedora repositories dnf would use. The first search on a new machine took 35 seconds while it downloaded repository metadata, and later searches reuse the cache.

rpm-ostree search htop

Matches come back grouped by how they matched, name matches first. Pick the exact package name; pcp-htop is the Performance Co-Pilot viewer, a different tool.

===== Name Matched =====
htop : Interactive process viewer
pcp-htop : Performance Co-Pilot (PCP) interactive process viewer

Install with sudo rpm-ostree install. In a terminal on the desktop itself, members of wheel can leave out sudo: Fedora’s polkit rule for rpm-ostree allows installs, removals, rollbacks and cleanups from a local, active session without a password. An SSH login is not a local session, so the rule does not match and the daemon refuses the request; the commands here use sudo throughout:

sudo rpm-ostree install htop

The transaction took 1 minute 42 seconds for two packages totalling 2.5 MB. Most of that time goes into composing a new OS tree rather than downloading, and the last line is the one to read.

Enabled rpm-md repositories: fedora-cisco-openh264 updates-testing updates fedora updates-archive
Importing rpm-md...done
rpm-md repo 'fedora-cisco-openh264' (cached); generated: 2026-03-24T10:56:00Z solvables: 3
rpm-md repo 'updates-testing' (cached); generated: 2026-10-15T01:53:22Z solvables: 14415
rpm-md repo 'updates' (cached); generated: 2018-02-20T19:18:14Z solvables: 0
rpm-md repo 'fedora' (cached); generated: 2026-10-16T08:34:13Z solvables: 75855
rpm-md repo 'updates-archive' (cached); generated: 2026-08-21T13:38:41Z solvables: 0
Resolving dependencies...done
Will download: 2 packages (2.5 MB)
Downloading from 'fedora'...done
Importing packages...done
Checking out packages...done
Running systemd-sysusers...done
Running pre scripts...done
Running post scripts...done
Running posttrans scripts...done
Writing rpmdb...done
Writing OSTree commit...done
Staging deployment...done
Added:
  htop-3.5.3-1.fc45.x86_64
  hwloc-libs-2.14.0-3.fc45.x86_64
Changes queued for next boot. Run "systemctl reboot" to start a reboot

Read the Enabled rpm-md repositories line on your own machine before you layer anything. On our 45 machine it included updates-testing with 14,415 packages, while the regular updates repo held none, so layered packages could resolve against testing builds. That is how a Fedora release looks until its stable updates repository opens; the 44 machine’s list had no updates-testing at all. When updates-testing shows up and you want stable builds only, that is a signal to wait or to keep the layer small.

Nothing changed on the running system yet. A new deployment now sits above the booted one and lists the layered package, while htop itself isn’t on the PATH.

rpm-ostree status; command -v htop || echo "htop not on PATH yet"

Look for Diff: 2 added and LayeredPackages: htop on the pending entry. The booted entry, marked with the dot, has neither until you reboot.

State: idle
Deployments:
  fedora:fedora/45/x86_64/silverblue
                  Version: 45.20261016.n.0 (2026-10-16T07:41:13Z)
               BaseCommit: 26d3e5416fd143c2c3137ddaaeb5d2a024fa6db2b4e2db722462f43296eab286
             GPGSignature: Valid signature by 4F50A6114CD5C6976A7F1179655A4B02F577861E
                     Diff: 2 added
          LayeredPackages: htop

● fedora:fedora/45/x86_64/silverblue
                  Version: 45.20261016.n.0 (2026-10-16T07:41:13Z)
                   Commit: 26d3e5416fd143c2c3137ddaaeb5d2a024fa6db2b4e2db722462f43296eab286
             GPGSignature: Valid signature by 4F50A6114CD5C6976A7F1179655A4B02F577861E

Use a layered package without rebooting

A reboot is the safe way to start using the new deployment, but apply-live copies the pending changes into the running system. It is purely additive by default, and anything that replaces existing files needs --allow-replacement, so we keep it for new packages and reboot for removals and overrides. You can also pass --apply-live (or -A) to rpm-ostree install to do both steps in one go:

sudo rpm-ostree apply-live
htop --version

Our live update took 2 seconds and htop ran straight away. It was still there after the next reboot, because the pending deployment already contained it; apply-live only saves you the wait.

Computing /etc diff to preserve...done
Updating /usr...done
Updating /etc...done
Running systemd-tmpfiles for /run and /var...done
Added:
  htop-3.5.3-1.fc45.x86_64
  hwloc-libs-2.14.0-3.fc45.x86_64
Successfully updated running filesystem tree.

Skip weak dependencies with Recommends=false

Layering pulls in recommended packages by default, and on Silverblue every extra package is something future updates must carry. There is no command line flag for this; the switch is a line in /etc/rpm-ostreed.conf. A dry run of virt-manager shows how big the difference is. The counts include the two htop packages already pending in the same layer:

Setting in /etc/rpm-ostreed.confPackages in the dry runWhat you get
Default (#Recommends=true)160virt-manager plus the libvirt daemon, QEMU and their recommends
Recommends=false22virt-manager and its client libraries only, no libvirt daemon

A lean result isn’t a smaller version of the same thing, and the setting applies to every later layer until you remove it. With Recommends=false you get a virt-manager that can only manage a remote host, because the local hypervisor came in as a weak dependency. Set it when you know exactly which packages you need, and list the daemon explicitly when you do. Add the line under the existing [Daemon] section and reload the daemon.

sudo sh -c "printf \"Recommends=false\n\" >> /etc/rpm-ostreed.conf"
sudo rpm-ostree reload
sudo rpm-ostree install --dry-run virt-manager

A dry run prints the full package list and then exits without staging anything. Run it before any large layer so you see the real cost first.

Resolving dependencies...done
Installing 22 packages:

To go back to the default, delete the line and reload the daemon again:

sudo sed -i "/^Recommends=false$/d" /etc/rpm-ostreed.conf
sudo rpm-ostree reload

Our next dry run of virt-manager was back to 160 packages.

Remove or replace a base package

Packages that ship in the image can’t be uninstalled, only hidden. rpm-ostree override remove builds a deployment without them, and the base image underneath keeps them, which is why the change is reversible. A common use is swapping the Firefox RPM for the Flathub build, so you get browser updates from Mozilla instead of waiting for the next OS compose.

sudo rpm-ostree override remove firefox firefox-langpacks

Afterwards the status output carries a RemovedBasePackages line next to the layered htop.

State: idle
Deployments:
  fedora:fedora/45/x86_64/silverblue
                  Version: 45.20261016.n.0 (2026-10-16T07:41:13Z)
               BaseCommit: 26d3e5416fd143c2c3137ddaaeb5d2a024fa6db2b4e2db722462f43296eab286
             GPGSignature: Valid signature by 4F50A6114CD5C6976A7F1179655A4B02F577861E
                     Diff: 2 removed
      RemovedBasePackages: firefox firefox-langpacks 157.0-1.fc45
          LayeredPackages: htop

override replace swaps a base package for a different build, from a local RPM or a URL. Fedora’s documentation uses Koji links for this, and it’s how you hold back a single package that regressed. We downgraded jq by one build to see what the system records.

sudo rpm-ostree override replace https://kojipkgs.fedoraproject.org/packages/jq/1.8.2/4.fc45/x86_64/jq-1.8.2-4.fc45.x86_64.rpm

Now all three kinds of customisation sit stacked on the same base commit.

State: idle
Deployments:
  fedora:fedora/45/x86_64/silverblue
                  Version: 45.20261016.n.0 (2026-10-16T07:41:13Z)
               BaseCommit: 26d3e5416fd143c2c3137ddaaeb5d2a024fa6db2b4e2db722462f43296eab286
             GPGSignature: Valid signature by 4F50A6114CD5C6976A7F1179655A4B02F577861E
                     Diff: 1 downgraded, 2 removed
      RemovedBasePackages: firefox firefox-langpacks 157.0-1.fc45
           LocalOverrides: jq 1.8.2-5.fc45 -> 1.8.2-4.fc45
          LayeredPackages: htop

Overrides are pinned by design. The jq replacement stays at that build through every update until you run sudo rpm-ostree override reset jq, so treat each LocalOverrides line as a reminder to check for a fixed build. Leave one in place for months and you are running an old package that no update will touch.

Undo changes with uninstall, rollback and reset

Three commands take things back, and they work at different scopes:

  • sudo rpm-ostree uninstall htop removes one layered package (1 minute 42 seconds on our rebased machine)
  • sudo rpm-ostree rollback makes the previous deployment the default for the next boot
  • sudo rpm-ostree reset drops every layer, removal and override in one transaction

Rollback is fast because it builds nothing. After rebooting into the customised deployment, the command took 3.6 seconds and printed what the swap would change.

sudo rpm-ostree rollback

Firefox and the newer jq come back on the next boot. Nothing is deleted either way, since the customised deployment stays one rollback away.

Moving '4c9ea3455c893caf8cb453ef01a4a41611efe5baace7c19425c2587715842f7e.0' to be first deployment
Transaction complete; bootconfig swap: no; bootversion: boot.0.1, deployment count change: 0
Upgraded:
  jq 1.8.2-4.fc45 -> 1.8.2-5.fc45
Added:
  firefox-157.0-1.fc45.x86_64
  firefox-langpacks-157.0-1.fc45.x86_64
Changes queued for next boot. Run "systemctl reboot" to start a reboot

Running rollback a second time swapped the order back, because it only toggles between the two deployments. Silverblue keeps just one rollback target by default, so the next update replaces it. Fedora’s documentation adds a warning worth taking seriously: updating after a rollback undoes the rollback, and GNOME Software updates on its own, so pause automatic updates if you need to stay on the older one, and pin it so it isn’t pruned. For a temporary rollback without any command, pick the older entry in the GRUB menu at boot.

reset is the one to reach for when you have lost track of what you changed. It took 26 seconds and returned the machine to the plain image, removing the htop layer, restoring Firefox and undoing the jq override:

sudo rpm-ostree reset

Its summary lists all three changes reversed at once. Flatpaks and toolboxes are not part of it: the Flathub Firefox and the 45 toolbox were still there after the reset, a reboot and two image switches.

Staging deployment...done
Upgraded:
  jq 1.8.2-4.fc45 -> 1.8.2-5.fc45
Removed:
  htop-3.5.3-1.fc45.x86_64
  hwloc-libs-2.14.0-3.fc45.x86_64
Added:
  firefox-157.0-1.fc45.x86_64
  firefox-langpacks-157.0-1.fc45.x86_64
Changes queued for next boot. Run "systemctl reboot" to start a reboot

Install desktop apps with Flatpak

Flatpak is where desktop apps belong on Silverblue, and the defaults surprised us. A kickstart install that skips the first-login setup has exactly one Flatpak remote, Fedora’s own OCI registry, and the preinstalled GNOME apps come from it. Flathub isn’t configured until you add it or accept the third party repositories prompt in GNOME Software, so most apps you’d look for simply aren’t offered yet.

flatpak remotes --show-details

Only the fedora remote is listed. Its preinstalled apps ran on the f44 branch of the Fedora Platform runtime even on the 45 machine, so Fedora’s Flatpaks don’t move in step with the OS.

fedora	-	oci+https://registry.fedoraproject.org	-	-	-	1	system,oci	-	-	-	-

GNOME Software asks about third party sources the first time it opens. Its Enable button runs fedora-third-party, which on our 45 machine managed an unfiltered Flathub remote plus RPM Fusion’s Steam and NVIDIA driver repositories; after we accepted it, rpm-ostree started reading rpmfusion-nonfree-steam and rpmfusion-nonfree-nvidia-driver as well.

GNOME Software on Fedora Silverblue 45 asking to enable third party software repositories

From the terminal, add Flathub directly. A system wide remote needs root, and it makes Flathub apps available to every user on the machine.

sudo flatpak remote-add --if-not-exists flathub https://dl.flathub.org/repo/flathub.flatpakrepo
flatpak remotes

Both remotes now show as system installations. Apps installed from either one land in /var/lib/flatpak, which survives OS upgrades, rebases and rollbacks untouched.

fedora	system,oci
flathub	system

Our first flatpak search firefox right after adding the remote printed No matches found, because search reads the AppStream catalogue and nothing had downloaded it yet. Refresh it once and search works.

sudo flatpak update --appstream flathub
flatpak search --columns=application,version,remotes firefox | head -4

Flathub’s Firefox was one major version ahead of the RPM in the image.

org.mozilla.firefox	158.0	flathub
dev.qwery.AddWater	1.3	flathub
one.ablaze.floorp	12.20.1	flathub
io.github.gamingdoom.Datcord	0.6.2	flathub

Install it with the application ID. The download took 75 seconds, including the Freedesktop runtime it shares with other Flathub apps.

sudo flatpak install -y flathub org.mozilla.firefox
flatpak info org.mozilla.firefox | grep -E "Runtime|Installed|Origin|Version"

On disk, the app alone takes 345 MB. With the preinstalled Fedora apps and both runtimes, /var/lib/flatpak had grown to 3.5 GB, which is worth knowing on a small root disk.

       Version: 158.0
        Origin: flathub
Installed Size: 345.1 MB
       Runtime: org.freedesktop.Platform/x86_64/25.08

GNOME Software shows which source an app comes from under the install button. With the Firefox RPM still in the image, the Firefox page reads Fedora Linux, and the drop-down next to it is where GNOME Software lets you pick another source when more than one carries the app.

GNOME Software on Fedora Silverblue 45 showing Firefox with the Fedora Linux package source

Day to day you’ll only need three Flatpak commands:

  • flatpak update updates every app and runtime
  • flatpak uninstall APP_ID removes one app
  • flatpak uninstall --unused clears runtimes nothing uses any more

Run them from a terminal on the desktop or let GNOME Software do it. Over SSH we couldn’t update Fedora’s own Flatpaks at all: as the user, reads of the OCI remote failed with GenerateOciSummary not allowed for user, and with sudo the update stopped with Cannot autolaunch D-Bus without X11 $DISPLAY. Unlike rpm-ostree, sudo doesn’t get you past that, and we didn’t run the desktop update to completion, so treat remote Flatpak updates on Silverblue as untested. The full workflow, including user installations and permissions, is in our guide to Flatpak and Flathub on Fedora.

Use Toolbx for command line tools

Toolbx gives you a mutable Fedora container that shares your home directory, user and terminal, so dnf works exactly as it does on Workstation. Compilers, language runtimes and anything you install for one project belong there. Create the default container, which matches the host release.

toolbox create -y
toolbox list

Creating it took 45 seconds, most of it pulling the image. A container for another release pulls its own image, and the 44 one below took 38 seconds.

IMAGE ID      IMAGE NAME                                    CREATED
5c93179c8737  registry.fedoraproject.org/fedora-toolbox:45  13 hours ago

CONTAINER ID  CONTAINER NAME     CREATED        STATUS   IMAGE NAME
39560ba9886e  fedora-toolbox-45  3 seconds ago  created  registry.fedoraproject.org/fedora-toolbox:45

Run toolbox enter for an interactive shell, or toolbox run for a single command. Installing GCC and make inside the container took 74 seconds, and the host stayed untouched.

toolbox run sudo dnf -y -q install gcc make
toolbox run gcc --version | head -1
command -v gcc || echo "gcc: not on the host"

GCC exists only inside the toolbox. On the host the same lookup fails, which is the point: the next OS update has nothing extra to carry.

gcc (GCC) 16.2.1 20260819 (Red Hat 16.2.1-2)
gcc: not on the host

A toolbox doesn’t follow the host through upgrades, and that works in your favour. A container created with --release 44 keeps building against Fedora 44 after the host moves to 45, which is handy when a project has to match an older target.

toolbox create -y --release 44 f44-build
toolbox run -c f44-build rpm -E %fedora

That container reports 44. Cleaning up takes two steps, because removing a container leaves its image behind:

  • toolbox rm -f f44-build deletes the container
  • toolbox rmi with the image name deletes the image as well

Skip the second step and the images pile up. Each Fedora toolbox image takes about 2.2 GB, and after our test podman images still listed both the 45 and the 44 image. If you want other distributions in the same way, Distrobox and Toolbx compare closely on speed and disk.

Update Silverblue and check the staged deployment

Fedora’s documentation says GNOME Software downloads OS updates on Silverblue automatically and tells you when a restart is due, so most desktop users never type an update command. On our freshly rebased machine it hadn’t done so yet, and the page still offered a Download button. The Updates page splits the two worlds clearly: System Updates is the new OS image and needs a restart, while App Updates are Flatpaks that update in place.

GNOME Software Updates page on Fedora Silverblue 45 with a system update and Fedora Flatpak app updates

That screen came from the machine we had just rebased to 45, and it shows something worth knowing: the OS moved to 45 while Fedora’s preinstalled Flatpak apps were still the GNOME 50 builds, waiting as separate app updates. A rebase never touches Flatpaks, so plan on a separate app update after every major upgrade. From a terminal, check first and then apply.

sudo rpm-ostree upgrade --check

A check fetches only metadata and summarises the size of the update. Ours showed 482 upgraded, 26 removed and 11 added packages, a large jump because the machine was still on the ISO’s original tree.

2 metadata, 0 content objects fetched; 64 KiB transferred in 5 seconds; 0 bytes content written
Writing objects: 1...done
Note: --check and --preview may be unreliable.  See https://github.com/coreos/rpm-ostree/issues/1579
AvailableUpdate:
        Version: 45.20261016.n.0 (2026-10-16T07:41:13Z)
         Commit: 26d3e5416fd143c2c3137ddaaeb5d2a024fa6db2b4e2db722462f43296eab286
   GPGSignature: Valid signature by 4F50A6114CD5C6976A7F1179655A4B02F577861E
           Diff: 482 upgraded, 26 removed, 11 added

Then run the upgrade itself. You can keep working while it runs, because it writes a new deployment instead of replacing files under you.

sudo rpm-ostree upgrade

On 45 the update fetched 1.39 GB and took 9 minutes 44 seconds. The same step on 44 took 10 minutes for 648 upgraded packages.

2968 metadata, 18401 content objects fetched; 1360213 KiB transferred in 560 seconds; 3.1 GB content written
Scanning metadata: 1...done
Staging deployment...done

Before rebooting, rpm-ostree db diff lists every package the new deployment changes, and rpm-ostree status shows it above the booted one. The reboot itself is a normal boot into the new tree, with the old deployment kept as the rollback. On our 2 GB /boot partition, two deployments used 258 MB, so space is not a concern until you start pinning several.

Upgrade Silverblue 44 to 45 with rpm-ostree rebase

A major upgrade is a rebase to the next release’s branch. Fedora’s documentation asks for the latest update of the current release and a reboot first, and for one release at a time. Skipping releases is untested, so an older machine steps through each release in turn. List the branches the remote offers to confirm the name.

source /etc/os-release
ostree remote refs ${ID} | grep $(arch) | grep ${VARIANT_ID} | tail -6

We saw the 45 branch next to its testing branch.

fedora:fedora/44/x86_64/silverblue
fedora:fedora/44/x86_64/testing/silverblue
fedora:fedora/44/x86_64/updates/silverblue
fedora:fedora/45/x86_64/silverblue
fedora:fedora/45/x86_64/testing/silverblue
fedora:fedora/rawhide/x86_64/silverblue

Pin the current deployment before rebasing. OSTree keeps only two deployments, so after the rebase and one update on 45 your working 44 system would otherwise be gone. Index 0 is the booted deployment only when nothing is staged, and GNOME Software may have staged an update in the background, so read the index from rpm-ostree status -v first and replace 0 with the booted deployment’s index if something is staged.

sudo ostree admin pin 0

OSTree confirms it, and rpm-ostree status -v marks the deployment Pinned: yes.

Deployment 0 is now pinned

Get RPM Fusion out of the way before you rebase

Our 44 machine had RPM Fusion layered the way Fedora’s documentation installs it the first time, from the versioned release RPMs, plus htop. Those URLs install local packages, and rpm-ostree status lists them as LocalPackages: rpmfusion-free-release-44-3.noarch instead of layered packages. Our first rebase attempt ran with that setup.

sudo rpm-ostree rebase fedora:fedora/45/x86_64/silverblue

It downloaded the whole 45 tree in 486 seconds and then failed while reading repository metadata.

4013 metadata, 28816 content objects fetched; 1703386 KiB transferred in 486 seconds; 4.0 GB content written
Receiving metadata objects: 1/(estimating) 78 bytes/s 236 bytes...done
Checking out tree 26d3e54...done
Enabled rpm-md repositories: fedora-cisco-openh264 updates fedora rpmfusion-free-updates rpmfusion-free rpmfusion-nonfree-updates rpmfusion-nonfree updates-archive
Updating metadata for 'fedora-cisco-openh264'...done
Updating metadata for 'updates'...done
Updating metadata for 'fedora'...done
Updating metadata for 'rpmfusion-free-updates'...done
error: Updating rpm-md repo 'rpmfusion-free-updates': cannot update repo 'rpmfusion-free-updates': Cannot prepare internal mirrorlist: Status code: 404 for https://mirrors.rpmfusion.org/metalink?repo=free-fedora-updates-released-45&arch=x86_64 (IP: 78.47.223.143); Last error: Status code: 404 for https://mirrors.rpmfusion.org/metalink?repo=free-fedora-updates-released-45&arch=x86_64 (IP: 78.47.223.143)

Any enabled repository that has no tree for the target release stops a rebase, because rpm-ostree must resolve your layered packages against the new release before it can write the deployment. RPM Fusion had published its base 45 repository but not its 45 updates repository, which is how its repos look until a release’s updates open, and it didn’t matter that htop came from Fedora: one unreachable repo is enough. A third party repository can trail a new Fedora release, so check yours before you start a rebase, not after the download.

for r in free-fedora-45 free-fedora-updates-released-45; do curl -s -o /dev/null -w "%{http_code} $r\n" "https://mirrors.rpmfusion.org/metalink?repo=$r&arch=x86_64"; done

A 404 on the updates line means the rebase will fail the same way.

200 free-fedora-45
404 free-fedora-updates-released-45

Getting past it takes two steps. The first doesn’t clear the 404, and it only applies if you installed RPM Fusion from the versioned release URLs, so the packages show under LocalPackages: Fedora’s documentation turns the local release RPMs into ordinary layered packages, so they follow the rebase to the 45 release package instead of staying pinned to the 44 files. Run it and reboot.

sudo rpm-ostree update --uninstall rpmfusion-free-release --uninstall rpmfusion-nonfree-release --install rpmfusion-free-release --install rpmfusion-nonfree-release
sudo systemctl reboot

Second, the part that clears the 404: while the updates metalink still returns 404, disable the two RPM Fusion updates repositories for the rebase. Only enabled= lines in files under /etc change, and the debuginfo and source sections in the same files ship disabled in RPM Fusion’s release package.

sudo sed -i "s/^enabled=1/enabled=0/" /etc/yum.repos.d/rpmfusion-free-updates.repo /etc/yum.repos.d/rpmfusion-nonfree-updates.repo
sudo rpm-ostree rebase fedora:fedora/45/x86_64/silverblue

Our retry finished in 2 minutes 5 seconds. The failed attempt had left the 45 tree in the local repository, so nothing had to be downloaded again.

2 metadata, 0 content objects fetched; 828 B transferred in 4 seconds; 0 bytes content written
Checking out tree 26d3e54...done
Enabled rpm-md repositories: fedora-cisco-openh264 updates fedora rpmfusion-free rpmfusion-nonfree updates-archive

After the reboot the machine ran 45 with both layers carried over, and rpm -q showed the release package had moved to rpmfusion-free-release-45-0.2.noarch. The pinned 44 deployment stayed in the boot menu as a fallback.

State: idle
Deployments:
● fedora:fedora/45/x86_64/silverblue
                  Version: 45.20261016.n.0 (2026-10-16T07:41:13Z)
               BaseCommit: 26d3e5416fd143c2c3137ddaaeb5d2a024fa6db2b4e2db722462f43296eab286
             GPGSignature: Valid signature by 4F50A6114CD5C6976A7F1179655A4B02F577861E
          LayeredPackages: htop rpmfusion-free-release rpmfusion-nonfree-release

  fedora:fedora/44/x86_64/silverblue
                  Version: 44.20261016.0 (2026-10-16T00:41:32Z)
               BaseCommit: 200e05c0e5f540d0ce3f7498648519f08361414136264ea026c97d6fb36b5aaa
             GPGSignature: Valid signature by 36F612DCF27F7D1A48A835E4DBFCF71C6D9F90A6
          LayeredPackages: htop rpmfusion-free-release rpmfusion-nonfree-release

  fedora:fedora/44/x86_64/silverblue
                  Version: 44.20261016.0 (2026-10-16T00:41:32Z)
               BaseCommit: 200e05c0e5f540d0ce3f7498648519f08361414136264ea026c97d6fb36b5aaa
             GPGSignature: Valid signature by 36F612DCF27F7D1A48A835E4DBFCF71C6D9F90A6
          LayeredPackages: htop
            LocalPackages: rpmfusion-free-release-44-3.noarch
                           rpmfusion-nonfree-release-44-3.noarch
                   Pinned: yes

Leave the updates repositories disabled until the curl check returns 200. We re-enabled one on 45 to test it, and rpm-ostree upgrade failed with the same 404, which means every background update GNOME Software tries would fail too. Don’t forget them once the check passes, though: with them off you get no RPM Fusion security updates, and a newer release package won’t reset the edited files. This turns only the first section of each file back on, the main updates repo, and leaves debuginfo and source off:

sudo sed -i "0,/^enabled=0/s//enabled=1/" /etc/yum.repos.d/rpmfusion-free-updates.repo /etc/yum.repos.d/rpmfusion-nonfree-updates.repo

Then run sudo rpm-ostree upgrade to confirm the repositories resolve. The general version of our Fedora 44 to 45 upgrade guide covers Workstation; for codecs on top of RPM Fusion, see RPM Fusion and multimedia codecs on Fedora.

Unpin and clean up old deployments

Once 45 has proved itself, release the pin. Find the index right before you unpin, because staging a new deployment shifts every index by one. We unpinned index 2 from an old listing and got Deployment 2 is already unpinned, because the pinned 44 tree had moved to index 3, so the command did nothing. Reading the index from the JSON output avoids the guesswork.

idx=$(rpm-ostree status --json | jq "[.deployments[] | .pinned] | index(true)")
echo "pinned index: $idx"
sudo ostree admin pin --unpin $idx
sudo rpm-ostree cleanup --rollback

Our cleanup removed the old 44 deployments and freed 4.1 GB. It also leaves 45 without a rollback target until the next update creates one, so run it only once you’re sure.

pinned index: 2
Deployment 2 is now unpinned
Bootloader updated; bootconfig swap: yes; bootversion: boot.1.1, deployment count change: -2
Freed: 4.1 GB (pkgcache branches: 4)

Switch Silverblue to the bootc container image

Fedora also publishes Silverblue as a bootable container image, quay.io/fedora/fedora-silverblue, and bootc is already installed. An ISO install does not use it, though. On both releases bootc status reports image: null, and sudo bootc upgrade --check fails with error: Upgrading: No image source specified on 45 and error: Upgrading: Missing image in specification on 44. The OSTree remote and rpm-ostree stay in charge until you switch the origin.

Should you switch at all? On a single desktop we wouldn’t, because you trade signed OSTree updates for unverified registry pulls and a much larger first download. Switch when you want to manage the machine from a Containerfile or need dnf’s --transient installs, both covered below. The obvious first attempt is the signed transport, which fails on a stock install because no signature policy for Fedora’s registry ships with it.

sudo rpm-ostree rebase ostree-image-signed:docker://quay.io/fedora/fedora-silverblue:45

With a default policy that accepts anything, the signed transport refuses to run. A policy that requires Fedora’s signatures for that registry would satisfy it, but we didn’t build and test one, so the switch below takes the unverified path.

Pulling manifest: ostree-image-signed:docker://quay.io/fedora/fedora-silverblue:45
error: Preparing import: Fetching manifest: containers-policy.json specifies a default of `insecureAcceptAnything`; refusing usage

bootc switch works with that default policy. It was also the slowest operation in this guide: 65 layers, 2.6 GB and 20 minutes 12 seconds, against 8 minutes 40 seconds for the 1.7 GB OSTree rebase on the same network.

sudo bootc switch quay.io/fedora/fedora-silverblue:45

Like any other deployment, it’s queued for the next boot. Deploying took 18 seconds of the 20 minutes; the rest was the download.

layers already present: 0; layers needed: 65 (2.6 GB)
Deploying...done (18 seconds)
Queued for next boot: quay.io/fedora/fedora-silverblue:45
  Version: 45.20261016.n.0
  Digest: sha256:0bf25e6c805ee002e0de3ef641dd7f1879fb50788ce984de0497bd4fca3ad643

Its version string matched the OSTree commit we were running, but the content didn’t. Three things changed with the switch:

  • Packages: rpm-ostree db diff showed fedora-repos-ostree gone and seven added, led by dnf5 and dnf5-plugins
  • Origin: ostree-unverified-registry:, so updates skip signature checks
  • Updates: bootc upgrade now works, and the OSTree commit becomes the rollback

From the next boot, bootc reports the image and handles updates, so check both.

sudo bootc status --format=humanreadable
sudo bootc upgrade --check

Now the OSTree commit is the rollback, and the upgrade check talks to the registry. Its answer, No changes in, means the registry tag still points at the digest you booted.

● Booted image: quay.io/fedora/fedora-silverblue:45
        Digest: sha256:0bf25e6c805ee002e0de3ef641dd7f1879fb50788ce984de0497bd4fca3ad643 (amd64)
       Version: 45.20261016.n.0 (2026-10-16T07:51:18Z)

  Rollback ostree
           Commit: 26d3e5416fd143c2c3137ddaaeb5d2a024fa6db2b4e2db722462f43296eab286
No changes in: docker://quay.io/fedora/fedora-silverblue:45

Test packages with dnf –transient

Because the container image ships dnf5, dnf exists on the host after the switch. A normal install is refused with Error: this bootc system is configured to be read-only. Pass --transient to perform this operation in a transient overlay which will reset when the system reboots. Adding --transient mounts an overlay on /usr for the current boot only. dnf writes into it during the transaction, and afterwards bootc status reports it as transient, read-only.

sudo dnf install -y --transient cowsay
rpm -q cowsay

dnf warns that the overlay covers /usr only, then installs normally.

A transient overlay will be created on /usr that will be discarded on reboot. Keep in mind that changes to /etc and /var will still persist, and packages commonly modify these directories.

After a reboot rpm -q cowsay answered package cowsay is not installed. That makes --transient the closest thing Silverblue has to trying a package and throwing it away, and it is faster than layering: 23 seconds for htop against 1 minute 42 seconds. Mind the warning, though. Files a package writes under /etc or /var outlive the overlay.

Build your own Silverblue image

On a host booted from the container image, the long-term replacement for layering is a Containerfile. Packages you add with dnf in the build become part of the OS image, so updates no longer re-resolve a layer on every machine, and the same file builds the same system everywhere. Create a build directory and the file.

mkdir -p ~/sb-image && cd ~/sb-image
vi Containerfile

Here is the file we built. Its rm line clears what dnf leaves in /var and /run; our first build without it passed lint with three warnings about exactly those files.

FROM quay.io/fedora/fedora-silverblue:45

RUN dnf -y install htop distrobox && \
    dnf clean all && \
    rm -rf /var/log/dnf5.log /var/lib/dnf /var/cache/libdnf5 \
           /var/cache/ldconfig/aux-cache /run/dnf

RUN bootc container lint

Build it as root, because bootc reads images from root’s container storage.

sudo podman build -t localhost/silverblue-custom:45 .

Our first build pulled the base image into Podman’s storage and took 7 minutes 46 seconds. The rebuild with the cleanup line took 4 minutes and finished with a clean lint.

Checks passed: 14
Checks skipped: 1
COMMIT localhost/silverblue-custom:45

Point the host at the local image. With the containers-storage transport, bootc reads it straight from Podman’s storage without a registry.

sudo bootc switch --transport containers-storage localhost/silverblue-custom:45

Only the two new layers had to be imported, so the switch took 58 seconds. The other 65 were already on disk from the upstream image, which is why rebuilding and switching is cheap after the first time.

layers already present: 65; layers needed: 2 (83.4 MB)
Deploying...done (22 seconds)
Pruned images: 0 (layers: 0, objsize: 78.7 MB)
Queued for next boot: ostree-unverified-image:containers-storage:localhost/silverblue-custom:45
  Version: 45.20261016.n.0
  Digest: sha256:bbea85ea5669954a8da2c3189198a282f2f9d9752a07007fc6b1f9784851f00c

After a reboot, bootc status shows containers-storage:localhost/silverblue-custom:45 as the booted image and the upstream image as the rollback. htop and distrobox are installed, yet rpm-ostree status lists no layered packages, because they are part of the image now. To change the image later, edit the Containerfile, rebuild under the same tag and run sudo bootc upgrade. We added tmux that way and the upgrade staged the new digest in 54 seconds, swapping one layer for an 84.9 MB one. Add --pull=always to the build when you also want a newer base image. Keep rpm-ostree install off a host you manage this way: after we layered fish on top, sudo bootc upgrade refused with Deployment contains local rpm-ostree modifications; cannot upgrade via bootc and pointed at rpm-ostree reset. Put the package in the Containerfile instead. For more than one machine, push the image to a registry and bootc switch each host to it; our Fedora Hummingbird hands-on runs a similar bootc image on a much smaller package set.

Fix common Silverblue package errors

Each of these came up during testing, with the exact message the system printed.

dnf: command not found

Expect it on an ISO install, because the OSTree image doesn’t include dnf. Use rpm-ostree install for host packages or toolbox run sudo dnf install for tools. Only a host switched to the bootc container image has dnf, and even there it needs --transient.

error: rpmostreed OS operation AutomaticUpdateTrigger not allowed for user

We got this from rpm-ostree upgrade --check over SSH. The polkit rule that ships with rpm-ostree only approves requests from a local, active session, and an SSH login is not local. Prefix the command with sudo.

Status code: 404 for https://mirrors.rpmfusion.org/metalink

An enabled repository has no metadata for the release you are moving to. Disable that repository until its metalink returns 200, then rebase or upgrade again. The rebase section has the check and the commands.

error: Upgrading: No image source specified (or Missing image in specification)

bootc upgrade only works after the host has been switched to a container image. On a system installed from the ISO, keep using rpm-ostree upgrade, or switch with sudo bootc switch quay.io/fedora/fedora-silverblue:45 first.

containers-policy.json specifies a default of insecureAcceptAnything

The ostree-image-signed: transport refuses to run while the default container policy accepts unsigned images, and a stock Silverblue install ships no Fedora signing policy. Use bootc switch or the ostree-unverified-registry: transport, knowing that updates are then not signature checked.

flatpak search returns No matches found

The remote is there, but its AppStream data is not. Run sudo flatpak update --appstream flathub and search again. It took one refresh on our 45 machine, and the next search returned Firefox at the top.

A package you layered is missing after an upgrade

Check rpm-ostree status before assuming it was dropped. Layered packages are reinstalled into every new deployment, so if one is missing, you are probably booted into the rollback or a pinned deployment, which the dot in the listing will show. Layers you add for one job, such as the FUSE packages older AppImages need on Silverblue, are easy to forget, so keep a note of what you layered and why.

Keep reading

How to Upgrade Fedora 44 to Fedora 45 (GUI, CLI and Rollback) Fedora How to Upgrade Fedora 44 to Fedora 45 (GUI, CLI and Rollback) 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 Switch Fedora From GRUB to systemd-boot With bootctl Fedora Switch Fedora From GRUB to systemd-boot With bootctl DNF5 Cheatsheet for Fedora 45 / 44: Tested Commands and Fixes Fedora DNF5 Cheatsheet for Fedora 45 / 44: Tested Commands and Fixes LUKS TPM2 Auto-Unlock on Fedora with systemd-cryptenroll Fedora LUKS TPM2 Auto-Unlock on Fedora with systemd-cryptenroll

3 thoughts on “Manage Packages on Fedora Silverblue 45 / 44 with rpm-ostree”

Leave a Comment

Press ESC to close