How to Layer Packages on Fedora Silverblue with rpm-ostree

Layer packages on Fedora Silverblue using the rpm-ostree install command and reboot.

The scenario

You switched to Fedora Silverblue because you wanted a system that stays stable and predictable. Then you try to install a missing codec, a development library, or a niche CLI tool. You run sudo dnf install and get a wall of text about read-only filesystems and transaction failures. The terminal refuses to let you modify the base system. You need the software, but the immutable design is blocking you.

How the immutable filesystem actually works

Silverblue does not use a traditional mutable filesystem for the operating system. The base OS lives on an ostree repository. Every package, configuration file, and kernel module is tracked by a single commit hash. When you boot, the system mounts that commit as a read-only filesystem. This design prevents accidental file corruption, stops dependency hell, and guarantees you can always roll back to a known-good state.

Think of it like a version-controlled document. You cannot edit the original file directly. Instead, you create a new version that includes your changes. rpm-ostree is the tool that manages those versions. When you ask it to add a package, it does not modify the running system. It calculates a new commit that includes the base OS plus your requested packages, stages it as a new boot entry, and waits for you to reboot. The running system stays untouched until you restart.

The transaction model separates the host from your modifications. Every time you layer a package, rpm-ostree writes a new deployment to disk. GRUB reads those deployments and presents them as boot options. The previous deployment stays intact. If the new layer breaks something, you select the older entry at boot and the system reverts instantly. No rescue USB required.

Convention aside: This is why you never see package managers like dnf or apt modifying /usr on Silverblue. The /usr directory is part of the ostree commit. All user modifications happen in /etc or through the rpm-ostree transaction pipeline. Trust the package manager. Manual file edits drift, snapshots stay.

Layering packages with rpm-ostree

The correct way to add software to the base system is through the rpm-ostree install command. This command stages a new deployment. It pulls the requested packages, resolves dependencies against the current base commit, and prepares a fresh boot entry.

Here is how to layer a package onto the system.

# Request rpm-ostree to stage a new deployment with the added package
sudo rpm-ostree install <package-name>
# The command calculates dependencies and downloads payloads
# It does not modify the running filesystem yet
# A new boot entry is created in GRUB

The transaction will show a progress bar as it downloads and verifies the packages. Once it finishes, you will see a message indicating that a new deployment is staged. The system will prompt you to reboot. Do not skip the reboot. The new deployment only becomes active after you restart and select the updated boot entry.

If you need to add multiple packages at once, pass them as separate arguments. The transaction resolver handles all of them in a single commit.

# Stage multiple packages in one atomic transaction
sudo rpm-ostree install ffmpeg git vim
# All packages are resolved together
# A single new boot entry is created
# Reduces transaction overhead and keeps the commit history clean

Convention aside: Always check the transaction status before rebooting. rpm-ostree sometimes pauses if a background update is running or if a previous transaction failed. Run rpm-ostree status to verify the staged deployment is ready. Reboot before you debug. Half the time the symptom is gone.

Verify the transaction completed

After the reboot, you need to confirm the system actually booted into the new deployment and that the package is available. The GRUB menu will show two entries. The top entry is the new deployment. The second entry is the previous deployment, kept for rollback.

Here is how to verify the active deployment and check the layered packages.

# Show the current boot status and deployment list
rpm-ostree status
# Displays the active commit hash
# Lists all deployments with their boot IDs
# Confirms which version the kernel is actually running

Look for the line that says Deployments:. The first entry under that heading is your active system. It will list the base OS version and any layered packages. You can also verify the package is installed by checking the RPM database directly.

# Query the RPM database to confirm the package is present
rpm -q <package-name>
# Returns the full package name and version
# Confirms the binary is available in the PATH
# Validates the transaction applied correctly

If the package shows up in both commands, the layering succeeded. The software is now part of the immutable base for this deployment. Run journalctl -xe if the service failed to start. Read the actual error before guessing.

Managing and removing layers

Layered packages persist across base OS upgrades. They do not disappear when you run rpm-ostree upgrade. If you no longer need a layered package, you must explicitly remove it. The removal process works exactly like installation. It stages a new deployment without the package, then waits for a reboot.

Here is how to remove a layered package.

# Stage a new deployment that excludes the specified package
sudo rpm-ostree uninstall <package-name>
# Calculates the reverse transaction
# Removes the package from the next commit
# Keeps the base OS untouched until reboot

The transaction will clean up orphaned dependencies automatically. If you layered a package that pulled in several libraries, rpm-ostree drops the entire dependency tree. You can verify the removal by running rpm-ostree status again after the reboot. The deployment list will show a fresh commit hash with fewer layered packages.

Convention aside: rpm-ostree upgrade handles base OS updates. It does not automatically update layered packages. You must run rpm-ostree upgrade to pull the latest base commit, then rpm-ostree install <package> again if you want to refresh the layered software. The transaction model separates base updates from user layers by design. Snapshot the system before the upgrade. Future-you will thank you.

Common pitfalls and error messages

Layering packages works smoothly until you hit a dependency conflict or a repository mismatch. The most common error occurs when you try to layer a package that conflicts with a core system component. rpm-ostree will refuse to stage the transaction and print a conflict report.

Error: Transaction test error: package python3-3.12.x conflicts with python3-3.13.y

The conflict is intentional. The base OS pins specific versions of core libraries to maintain stability. You cannot layer a newer version of a core library without breaking the atomic model. The solution is to use a container or a toolbox for that specific workload instead of modifying the base.

Another frequent issue is repository caching. If you recently added a custom repository or enabled a module, rpm-ostree might still be looking at stale metadata. The transaction will fail with a package-not-found error. Clear the cache and force a refresh before retrying.

# Force rpm-ostree to refresh repository metadata
sudo rpm-ostree refresh-md
# Downloads the latest repo indexes
# Clears stale package lists
# Prevents false package-not-found errors

SELinux denials also surface during layering if the new package tries to write to a protected directory. Check the audit logs before disabling the security policy.

# Check for SELinux denials related to the new package
sudo ausearch -m avc -ts recent
# Filters audit logs for access vector cache denials
# Shows exactly which process was blocked
# Helps you fix the context instead of disabling enforcement

Convention aside: Never disable SELinux to fix a layering error. The denials usually point to a missing file context or a misconfigured service. Fix the context or adjust the service unit. The security boundary is there for a reason. Run journalctl first. Read the actual error before guessing.

When to layer packages versus alternatives

You have three main ways to run software on Silverblue. Each one serves a different workflow. Pick the right tool for the job.

Use rpm-ostree install when you need a system-wide binary that must integrate with the base OS, such as a kernel module, a system service, or a CLI tool used by multiple users. Use toolbox when you need a mutable development environment with full dnf access, isolated from the host filesystem. Use podman when you are running stateful applications, databases, or services that require their own runtime dependencies. Stay on the base Silverblue image if you only need the default packages and want maximum stability.

Layering changes the boot entry. Every layered package increases the transaction size and slows down future upgrades. Keep the base system lean. Move development tools, IDEs, and heavy dependencies into containers. The host stays fast and predictable.

Where to go next