Press ESC to close Press / to search

dnf Module Streams on RHEL 8 and 9: Install Multiple App Versions the Right Way

🎯 Key Takeaways

  • dnf Module Streams on RHEL 8 and 9: Install Multiple App Versions the Right Way
  • What Is AppStream and Why Red Hat Introduced Module Streams
  • BaseOS vs AppStream: What Goes Where
  • dnf module list: See Available Modules and Streams
  • dnf module info: Understand What a Stream Provides

πŸ“‘ Table of Contents

dnf Module Streams on RHEL 8 and 9: Install Multiple App Versions the Right Way

One of the most frequently misunderstood features of RHEL 8 and RHEL 9 is the AppStream module system. Admins coming from Ubuntu or Debian are accustomed to PPAs and alternative package repositories for installing non-default versions of software. On RHEL, Red Hat built a more structured approach directly into the package manager: DNF module streams.

If you have ever tried to install PHP 8.2 on RHEL 9 and gotten an older version, or struggled to run multiple Python versions, this guide explains exactly why that happens and how the module system is designed to solve it. More importantly, it shows you how to use module streams correctly so you get the exact version you need without breaking your system or fighting dependency conflicts.

What Is AppStream and Why Red Hat Introduced Module Streams

RHEL has a ten-year support lifecycle. This creates a fundamental tension: the operating system needs to be stable and unchanging, but applications like PHP, Node.js, and Python release new major versions regularly and have much shorter support windows.

Before RHEL 8, Red Hat’s solution was Software Collections (SCL) β€” an add-on repository that installed additional versions of applications into /opt/rh/ and required a special scl enable command to activate them. It worked, but it was clunky and required every script and service definition to know about SCL.

AppStream module streams are Red Hat’s cleaner replacement. They allow multiple versions of a package to coexist in the same repository, organized into named streams, with one stream selected as the default at any time. When you enable a specific stream, all dnf install commands for that application automatically get packages from that stream β€” no special wrapper commands required.

The core design principle: RHEL’s BaseOS packages are stable and long-lived. AppStream packages can have shorter lifecycles, with different streams representing different supported versions.

BaseOS vs AppStream: What Goes Where

Understanding which repo a package comes from matters because it tells you about the package’s support lifecycle and stability expectations.

BaseOS

BaseOS contains the core operating system components that form the stable foundation of RHEL. These packages are supported for the full 10-year lifecycle of the RHEL major version without major version updates. Examples: kernel, glibc, systemd, OpenSSL, bash, Python 3 (the system Python), OpenSSH.

AppStream

AppStream contains user-space applications and development tools that have more rapid release cycles. AppStream packages are organized into modules with streams representing different versions. Examples: PHP 7.4, PHP 8.0, PHP 8.2, Node.js 18, Node.js 20, PostgreSQL 13, PostgreSQL 15, MariaDB 10.5, MariaDB 10.11.

# Verify both repos are enabled
$ dnf repolist
repo id                                    repo name
rhel-9-for-x86_64-appstream-rpms           Red Hat Enterprise Linux 9 for x86_64 - AppStream
rhel-9-for-x86_64-baseos-rpms             Red Hat Enterprise Linux 9 for x86_64 - BaseOS

dnf module list: See Available Modules and Streams

# List all available modules
$ dnf module list
Red Hat Enterprise Linux 9 for x86_64 - AppStream (RPMs)
Name         Stream       Profiles                Summary
mariadb      10.5         client, galera, server  MariaDB Module
mariadb      10.11 [d]    client, galera, server  MariaDB Module
maven        3.8 [d]      common                  Java project management tool
nodejs       18 [d]       common, development,    Javascript runtime
                          minimal, s2i
nodejs       20           common, development,    Javascript runtime
                          minimal, s2i
php          8.1 [d]      common, devel, minimal  PHP scripting language
php          8.2          common, devel, minimal  PHP scripting language
postgresql   13           client, server          PostgreSQL server and client module
postgresql   15 [d]       client, server          PostgreSQL server and client module
postgresql   16           client, server          PostgreSQL server and client module
python311    3.11 [d]     default                 Python 3.11

Hint: [d]efault, [e]nabled, [x]disabled, [i]nstalled

The hint at the bottom explains the symbols:

  • [d] β€” this is the default stream for this module. If you install the package without specifying a stream, this is what you get.
  • [e] β€” this stream is currently enabled on your system.
  • [x] β€” this stream is disabled.
  • [i] β€” packages from this module are installed.

dnf module info: Understand What a Stream Provides

# Get detailed information about a module and its streams
$ dnf module info php
Name             : php
Stream           : 8.1 [d][a]
Version          : 9010020230214152437.rhel9.1
Context          : rhel9
Architecture     : x86_64
Profiles         : common [d], devel, minimal
Default profiles : common
Repo             : rhel-9-for-x86_64-appstream-rpms
Summary          : PHP scripting language
Description      : PHP is an HTML-embedded scripting language. PHP attempts to make
                   it easy for developers to write dynamically generated webpages.
Artifacts        : apcu-panel-0:5.1.21-1.module+el9.1.0+13933+e44d7e05.noarch
                 : php-0:8.1.14-1.module+el9.1.0+17883+48b9a97d.x86_64
                 : php-bcmath-0:8.1.14-1.module+...x86_64
                 : php-cli-0:8.1.14-1.module+...x86_64
                 : php-common-0:8.1.14-1.module+...x86_64
                 : php-devel-0:8.1.14-1.module+...x86_64
                 : php-fpm-0:8.1.14-1.module+...x86_64

# Get info for a specific stream
$ dnf module info php:8.2

Installing a Specific Stream

Method 1: Enable the Stream Then Install

# Step 1: Enable the specific stream
$ dnf module enable php:8.2 -y
Dependencies resolved.
================================================================================
 Package          Architecture    Version             Repository           Size
================================================================================
Enabling module streams:
 php                              8.2

Transaction Summary
================================================================================

Complete!

# Step 2: Install PHP
$ dnf install php php-fpm php-cli -y

# Verify the installed version
$ php --version
PHP 8.2.15 (cli) (built: Jan 16 2024 13:04:52) (NTS)
Copyright (c) The PHP Group
Zend Engine v4.2.15, Copyright (c) Zend Technologies

Method 2: Install and Enable in One Command

# The @ syntax enables the stream and installs the default profile
$ dnf module install php:8.2 -y

# Or with a specific profile
$ dnf module install php:8.2/common -y

Understanding Module Profiles

Within each stream, there are profiles β€” predefined sets of packages for different use cases. Installing a profile installs a curated group of related packages for that purpose.

# Common PHP profiles
# common β€” installs php, php-cli, php-fpm, and common extensions
# devel   β€” installs everything in common plus headers and debugging tools
# minimal β€” installs only the core php package

# Install the devel profile to get headers for extension compilation
$ dnf module install php:8.2/devel -y

# For PostgreSQL, the profiles are:
# client β€” only the psql client and libraries
# server β€” the server and client
$ dnf module install postgresql:15/server -y

Real Scenario 1: Install PHP 8.2 Instead of Default 8.1

# Check what is currently installed
$ php --version 2>/dev/null || echo "PHP not installed"
PHP not installed

# Check the default stream
$ dnf module list php
Name  Stream   Profiles                Summary
php   8.1 [d]  common [d], devel, min  PHP scripting language
php   8.2      common [d], devel, min  PHP scripting language

# Enable PHP 8.2 and install
$ dnf module enable php:8.2 -y
$ dnf install php php-fpm php-mysqlnd php-mbstring php-xml php-json -y

# Verify
$ php --version
PHP 8.2.15 (cli) (built: Jan 16 2024 13:04:52) (NTS)

$ php -m | head -20
[PHP Modules]
bcmath
calendar
Core
ctype
curl
date
exif
fileinfo
filter
ftp
gettext
gmp
hash
iconv
json
libxml

# Start and enable php-fpm
$ systemctl enable --now php-fpm

Real Scenario 2: Run Python 3.11 Alongside System Python

On RHEL 9, the system Python is Python 3.9 from BaseOS. This is the Python that system tools depend on and should not be replaced. If you need Python 3.11 for your applications, the module system installs it alongside the system Python without replacing it.

# Check system Python
$ python3 --version
Python 3.9.18

# Check available Python modules
$ dnf module list python*
Name      Stream   Profiles  Summary
python311  3.11 [d]  default   Python 3.11

# Enable and install Python 3.11
$ dnf module enable python311:3.11 -y
$ dnf install python3.11 -y

# Python 3.11 is now available as python3.11 β€” system python3 is unchanged
$ python3 --version
Python 3.9.18

$ python3.11 --version
Python 3.11.7

# Create a virtual environment with Python 3.11 for your application
$ python3.11 -m venv /opt/myapp/venv
$ source /opt/myapp/venv/bin/activate
(venv) $ python --version
Python 3.11.7

This is the critical point: do not replace the system Python. RHEL’s system tools β€” dnf, ansible, and others β€” depend on the system Python. Installing Python 3.11 through the module system gives you the version you need without touching the system Python.

Real Scenario 3: PostgreSQL 15 vs Default Version

# Check available PostgreSQL streams
$ dnf module list postgresql
Name        Stream   Profiles         Summary
postgresql  13       client, server   PostgreSQL server
postgresql  15 [d]   client, server   PostgreSQL server
postgresql  16       client, server   PostgreSQL server

# Install PostgreSQL 16 (non-default stream)
$ dnf module enable postgresql:16 -y
$ dnf module install postgresql:16/server -y

# Initialize the database
$ postgresql-setup --initdb
$ systemctl enable --now postgresql

# Verify version
$ psql --version
psql (PostgreSQL) 16.1

$ sudo -u postgres psql -c "SELECT version();"
                                                       version
----------------------------------------------------------------------------------------------------------------------
 PostgreSQL 16.1 on x86_64-redhat-linux-gnu, compiled by gcc (GCC) 11.4.1 20231218 (Red Hat 11.4.1-3), 64-bit
(1 row)

Switching Between Streams

Switching between streams is possible but requires care. You must reset the current stream, enable the new stream, and then update the installed packages. This is not a trivial operation if you have data or configurations tied to the current version.

# Current situation: PHP 8.1 is installed and active
$ php --version
PHP 8.1.27

# Step 1: Remove the currently installed packages
$ dnf remove php php-fpm php-cli php-common -y

# Step 2: Reset the module (clears the stream selection)
$ dnf module reset php -y
Dependencies resolved.
Resetting modules:
 php
Complete!

# Step 3: Enable the new stream
$ dnf module enable php:8.2 -y

# Step 4: Install the packages from the new stream
$ dnf install php php-fpm php-cli -y

# Step 5: Verify
$ php --version
PHP 8.2.15

Important: Before switching PHP or database streams on a production server, always test in a staging environment. Major version upgrades can require configuration changes, deprecated function removal, and database schema migrations that are not handled automatically.

Resetting a Module Stream

# Reset a module completely (removes stream selection and all package installs)
$ dnf module reset php -y

# After reset, the module is back to unselected state
$ dnf module list php
Name  Stream   Profiles                Summary
php   8.1 [d]  common [d], devel, min  PHP scripting language
php   8.2      common [d], devel, min  PHP scripting language

When Modules Conflict With Each Other

DNF module streams enforce stream compatibility rules. If you try to enable two streams that conflict, dnf will refuse.

# Attempting to enable two conflicting streams
$ dnf module enable php:8.1 -y
Complete!

$ dnf module enable php:8.2 -y
Error: It is not possible to switch enabled streams of a module unless explicitly enabled via configuration option module_stream_switch.
Error: Problems in request:
missing groups or modules: php:8.2

# Fix: reset first, then enable the new stream
$ dnf module reset php -y
$ dnf module enable php:8.2 -y

The error message is clear: you cannot switch streams while another stream is enabled. Always reset before switching.

Disabling a Module Stream

# Disable a module stream (prevents packages from this stream being installed)
$ dnf module disable php:8.1 -y

# This prevents accidentally installing packages from the old stream
# Verify disabled status
$ dnf module list php
Name  Stream     Profiles                Summary
php   8.1 [x]   common [d], devel, min  PHP scripting language
php   8.2 [e]   common [d], devel, min  PHP scripting language

Node.js Module Streams

# Check available Node.js streams
$ dnf module list nodejs
Name    Stream   Profiles                           Summary
nodejs  18 [d]   common, development, minimal, s2i  JavaScript runtime
nodejs  20       common, development, minimal, s2i  JavaScript runtime

# Install Node.js 20
$ dnf module enable nodejs:20 -y
$ dnf install nodejs -y

$ node --version
v20.11.0

$ npm --version
10.2.4

Checking Module Status

# See which modules are currently enabled or installed on your system
$ dnf module list --enabled
Red Hat Enterprise Linux 9 for x86_64 - AppStream (RPMs)
Name        Stream   Profiles             Summary
nodejs      20 [e]   common, development  JavaScript runtime
php         8.2 [e]  common [d]           PHP scripting language
postgresql  16 [e]   server               PostgreSQL server

# See installed modules
$ dnf module list --installed

Common Beginner Mistakes

  • Installing a package without enabling the right stream first: If you run dnf install php without enabling a stream, you get the default stream version. Always enable the stream explicitly for non-default versions.
  • Trying to install two competing streams simultaneously: You cannot have PHP 8.1 and PHP 8.2 from module streams on the same system at the same time. Use containers if you need multiple major versions simultaneously.
  • Replacing the system Python: Never use the module system to replace /usr/bin/python3. Install the new version alongside it and use virtual environments.
  • Switching streams without resetting first: Always run dnf module reset before enabling a different stream. Trying to enable a stream while another is active results in an error.
  • Forgetting to install packages after enabling a stream: Enabling a stream does not install any packages. It just selects which version to use. You still need to run dnf install.
  • Not checking available streams before installing: Always run dnf module list <name> first to see what streams are available. Do not assume the latest version is the default.

Conclusion

DNF module streams are one of RHEL’s most practical features for server administrators who need specific versions of application stacks. The pattern is always the same: list available streams, enable the stream you need, install the packages, and verify. For switching versions, always reset the module first. The module system solves the version flexibility problem that previously required PPAs on Ubuntu or SCL on RHEL 7 β€” in a cleaner, more integrated way that plays well with DNF’s dependency resolver and Red Hat’s support model.

Was this article helpful?

Advertisement
🏷️ Tags: appstream dnf modules package-management RHEL
R

About Ramesh Sundararamaiah

Red Hat Certified Architect

Expert in Linux system administration, DevOps automation, and cloud infrastructure. Specializing in Red Hat Enterprise Linux, CentOS, Ubuntu, Docker, Ansible, and enterprise IT solutions.

🐧 Stay Updated with Linux Tips

Get the latest tutorials, news, and guides delivered to your inbox weekly.

Advertisement

Add Comment


↑