The EX294 exam objective 5.3 demands you understand how to manage software packages and repositories through Ansible. This concept solves the fundamental problem of consistently installing, updating, and removing software across hundreds of servers without manual, error-prone work. For an EX294 candidate, mastering this is critical because automation is the entire point of the exam, and package management is the most common task you will automate in the real world.
Jump to a section
A simple way to picture Software Package Management
A grocery store is a central repository where every product has a specific shelf location. If you want to make a specific dinner dish, you first decide what ingredients you need, like tomatoes, pasta, and cheese. You then go to the store, find each ingredient on the correct aisle, pick the exact brand and quantity, and bring it home. This is precisely how a software package manager works on a Linux system. Instead of physically walking aisles, a tool like yum or dnf contacts a remote server called a repository. That repository is a catalogue of software packages, each with a name, version, and list of dependencies (ingredients that other packages need). When you tell the package manager to install 'tomatoes' (e.g., the 'httpd' web server package), it checks the repository, finds the package, and then looks at its dependency list. It sees that 'httpd' needs 'libssl' (the cheese) and 'glibc' (the pasta) to work. The package manager then fetches all those required packages from the repository at once, exactly like bringing home all the ingredients for your recipe in one trip. It also handles upgrades automatically: when a new version of a package is available in the repository, it updates it just like a store restocking fresher produce. This organised, centralised, and automated process replaces the old, chaotic method of manually downloading and compiling software from random websites, ensuring your system stays consistent, secure, and functional.
The key difference from a grocery store is that a package manager also tracks what you have installed. It knows the exact version of every 'ingredient' on your system. This means it can intelligently resolve conflicts. If one recipe (package) needs 'flour version 2.0' but another needs 'flour version 1.5', the package manager will refuse to install both unless they are compatible or will suggest a compromise. This prevents the system from becoming a broken kitchen with incompatible ingredients.
Software package management is the process of installing, updating, configuring, and removing software on a computer in a controlled and automated way. Before package managers existed, system administrators had to find software on the internet, download an archive file (often a .tar.gz file), manually compile the source code (turn human-readable code into machine-executable binaries), and then copy the resulting files to the correct directories. This was slow, error-prone, and inconsistent. If a piece of software needed another piece of software to run (a dependency), the administrator had to find, compile, and install that dependency first. This was called 'dependency hell'.
A package manager solves this by introducing a centralised system. The core concepts are:
Package: A single, compressed file that contains all the files needed for a piece of software to run, plus metadata. Metadata includes the package's name, version, description, and its dependencies (a list of other packages it needs to work). For example, the httpd package (the Apache web server) is a collection of executable files, configuration templates, and documentation.
Repository: A server or a collection of servers that stores packages and a catalogue (an index) of those packages. This is like the grocery store's inventory system. On Red Hat Enterprise Linux (RHEL), the default repositories are provided by Red Hat and require a subscription. Organisations often create their own private repositories for internal software, which Ansible can then configure.
Dependency Resolution: The process by which the package manager automatically determines and installs all required supporting packages before installing the requested package. If you install httpd, the manager checks if libssl (a cryptographic library) is already installed. If not, it installs libssl automatically, then httpd. This happens recursively, meaning it will find dependencies of dependencies.
Package Manager (Tool): The actual software that talks to repositories and performs operations on packages. On RHEL 8 and 9, the default tool is dnf (Dandified YUM), which replaced the older yum (Yellowdog Updater, Modified). Both use the same underlying RPM (RPM Package Manager) format.
The RPM file format (.rpm) is the actual container for the software. You can manually download an RPM file and install it with the rpm command, but then you must handle dependencies yourself. The dnf tool is a higher-level tool that automatically resolves dependencies by consulting repositories. You can think of rpm as a powerful individual screwdriver and dnf as a power drill that also knows which screws (dependencies) you need.
Ansible interacts with this system through the dnf module (and the legacy yum module for older systems). The dnf module allows you to write a playbook that says: 'On all web servers, ensure the httpd package is present (installed) and at the latest version.' Ansible then connects to each target machine via SSH and executes the equivalent of dnf install httpd, but it does so idempotently. 'Idempotence' means that running the playbook multiple times produces the same result. If httpd is already installed, Ansible does nothing. If a newer version is available in the repository, it updates. If it is missing, it installs. This guarantees that the state of your servers matches what you defined in code.
The state parameter in the Ansible dnf module is crucial. It can be:
present: Ensures the package is installed, but does not update it if it already exists.
latest: Ensures the package is installed and at the most recent version available in the configured repositories.
absent: Ensures the package is removed.
removed: An alias for absent.
Ansible also allows you to manage the repositories themselves using the yum_repository module. This module adds, removes, or modifies repository configuration files (located in /etc/yum.repos.d/). For example, you can create a custom internal repository named 'internal-tools' that points to your company's private server.
Ansible's power here is that you write the desired end state once and apply it to hundreds of servers. You can also use variables to install different packages on different server types (e.g., httpd on web servers, postgresql on database servers) using the same playbook structure. This is the foundation of configuration management: defining infrastructure as code, where server configurations are version-controlled, testable, and repeatable. Without this, administrators would have to SSH into each server and run commands manually, leading to configuration drift (servers slowly becoming different over time).
Define the desired state in a variable
Before writing the playbook, decide what software you want on which machines. Create a variable, e.g., `web_packages: ['httpd', 'mod_ssl', 'php']`, stored in a file like `group_vars/web_servers.yml`. This separates configuration from code, making your playbook reusable for different server types.
Write the Ansible task using the dnf module
In your playbook, create a task that calls `ansible.builtin.dnf`. Set the `name` parameter to the variable (e.g., `{{ web_packages }}`) and `state: present` or `state: latest`. This is where you translate the business requirement (install PHP) into an Ansible instruction. The module handles the underlying `dnf` command and dependency resolution.
Add a handler to restart the service (if needed)
After installing a service like `httpd`, you often need to start it and enable it to start on boot. Use the `service` module in a separate task, or use a 'notify' directive to call a handler. A handler runs only when a change occurs (e.g., a package was installed or updated), preventing unnecessary restarts.
Optionally configure the repository
If the package you need is not in the default RHEL repositories (e.g., a custom internal tool), use the `yum_repository` module in a task before the package installation task. Set the `name`, `baseurl`, `description`, `gpgcheck`, and `enabled` parameters. This ensures that when the `dnf` module runs, it can find the package in the newly added repository.
Execute the playbook and verify the output
Run `ansible-playbook -i inventory your_playbook.yml`. Review the output: 'changed' means the dnf module actually performed an installation or update; 'ok' means the target was already compliant. You can also use `--check` mode to do a dry run without making changes, which is crucial for testing before a production deployment.
Consider a medium-sized e-commerce company called 'ShopFast'. They have 50 web servers running a PHP application and 10 database servers running PostgreSQL. The company is migrating to a new version of their PHP framework, which requires PHP 8.1 and several new PHP extensions.
The manual approach would be: a junior administrator logs into each of the 50 web servers, runs dnf install php8.1 php8.1-mbstring php8.1-xml on each one, and hopes they don't make a typo or miss a server. This takes hours, is mind-numbingly boring, and is prone to error.
With Ansible, the senior DevOps engineer writes a single playbook. The playbook contains two plays. The first play targets the group 'web_servers' and uses the dnf module to install packages. The second play targets 'db_servers' and uses the dnf module to install a different set of packages (e.g., postgresql14-server).
The steps the engineer takes are:
1. Define inventory: She updates the Ansible inventory file (/etc/ansible/hosts or a custom file) to group servers logically. For example:
[web_servers]
web01.example.com
web02.example.com
...
[db_servers]
db01.example.com
db02.example.comWrite the Playbook: She creates a file named update-php.yml. The playbook uses the dnf module with state: latest to ensure the packages are at the newest version. She also uses a yum_repository module to add the official Remi repository for PHP 8.1, as RHEL's default repositories may not have it.
Run the Playbook: She executes ansible-playbook -i inventory update-php.yml. Ansible connects to all 60 servers simultaneously (or within a controlled parallelism), checks the current state of each package, and only acts if needed.
Verification: Ansible returns a report showing 'changed' for servers that were updated and 'ok' for those already compliant. She can immediately see if any server failed (e.g., due to a network issue or a broken repository URL) and fix the specific problem.
This approach is not just faster. It is auditable. The playbook can be stored in a Git repository. When a change needs to be made, it goes through code review. If a bug is introduced, rolling back means reverting the Git commit and re-running the playbook. The company now has a standardised, reliable, and fast way to manage software across its entire infrastructure. This is the everyday reality for a Red Hat Certified Engineer: writing code that describes the desired state of servers and using Ansible to enforce that state.
The EX294 exam tests your ability to write Ansible playbooks that manage packages. You will not be asked to manually run dnf commands, but you must know the Ansible modules and their parameters perfectly. The exam environment uses RHEL 9, so the dnf module is the primary one. However, the legacy yum module can also appear, and you should know both exist.
Here is what EX294 specifically tests:
- Module names: You must use the correct module name. For RHEL 9, use dnf. For RHEL 8, both dnf and yum are valid. The exam typically specifies the OS version, so you adapt. Using yum when dnf is expected may still work (Ansible often handles aliases) but relying on that is risky.
- State parameter values: The exam loves to test the difference between present, latest, and absent. A common trap is using present when the requirement says 'ensure the package is the latest version'. Another trap is using absent when the question says 'remove the package if it exists', which is correct, but they might use removed as a synonym.
- Multiple packages: You will need to install or remove lists of packages. The name parameter accepts either a single string ('httpd') or a YAML list. The correct syntax for a list in a playbook is:
- name: Install packages
ansible.builtin.dnf:
name:
- httpd
- php
- mariadb-server
state: presentManaging repositories: The yum_repository module is tested. You must know how to add a repo. Key parameters: name (the repo ID, e.g., 'my-custom-repo'), description, baseurl (the URL of the repo), gpgcheck (set to yes to verify package signatures), gpgkey (URL of the GPG key), and enabled (set to no to disable the repo without deleting it).
Using variables for package names: They will often ask you to use a variable (defined in vars or a vars_file) for the package name. This tests your understanding of variable substitution within a module parameter.
Idempotence awareness: Multiple-choice questions may ask what happens if you run a playbook twice. The correct answer is that Ansible only changes what is out of state. If the package is already installed, dnf reports no change.
Common traps:
Forgetting to use the Fully Qualified Collection Name (FQCN) for modules. While plain dnf works in many contexts, the exam expects ansible.builtin.dnf.
Using the rpm module incorrectly. The rpm module does exist but it is low-level and does not handle dependencies. The exam will almost never test rpm directly because it is not the Idempotent, dependency-aware way.
Confusing name and pkg as parameter names. The dnf module uses name. The older yum module also used name.
Missing the state: absent requirement but failing to also handle dependencies. The exam may ask you to remove a package and all its dependencies. The dnf module has an autoremove parameter (set to yes) that removes unused dependencies, but they rarely test this deeply. Usually, just state: absent is enough.
Key definitions to memorise:
- Package: A file (.rpm) containing software and metadata.
- Repository: A collection of packages and their metadata, accessible via a URL.
- Dependency: A package required by another package to function.
- Idempotence: Running the operation multiple times results in the same final state.
- FQCN: Fully Qualified Collection Name, e.g., ansible.builtin.dnf.
The `dnf` module in Ansible is the primary tool for managing packages on RHEL 9, and it requires the `name` parameter for package names and the `state` parameter for desired status.
Using `state: present` installs a package but does not update it; only `state: latest` ensures the package is at the most recent version available in the repository.
Idempotence means running the same playbook multiple times will not change the system once it is in the desired state, and the `dnf` module reports 'ok' instead of 'changed' for packages already compliant.
To remove a package and its unused dependencies in one step, use both `state: absent` and `autoremove: yes` in the same task.
Managing repositories is done with the `yum_repository` module, which writes configuration files in `/etc/yum.repos.d/` on target hosts, not on the control node.
Always use the Fully Qualified Collection Name `ansible.builtin.dnf` for the dnf module to ensure clarity and compatibility with the exam environment.
These come up on the exam all the time. Here's how to tell them apart.
ansible.builtin.dnf
Specific to Red Hat family (RHEL, CentOS, Fedora).
Uses the dnf (or yum) command-line tool under the hood.
Can use RHEL-specific options like `autoremove` and `disablerepo`.
ansible.builtin.package
Generic across all Linux distributions (uses apt, yum, zypper, etc.).
Automatically detects the package manager of the target OS.
May not support all distribution-specific features or options.
state: present
Ensures the package is installed, but does NOT upgrade it.
If the package is present but a newer version exists, it remains at the current version.
Useful for pinning a specific version to avoid unexpected upgrades.
state: latest
Ensures the package is installed AND at the newest version available in the repository.
Will upgrade the package if a newer version exists.
Cannot be used with a specific version string in the `name` parameter because it will attempt to upgrade past it.
yum_repository module
Used to add, remove, or modify repository configuration files on target hosts.
Operates on files in `/etc/yum.repos.d/`.
Does not install software; it provides the source for the dnf module to use.
dnf module
Used to install, update, and remove software packages.
Consults repositories configured by yum_repository or system defaults.
Resolves dependencies automatically.
remove a package with state: absent
Only removes the specified package.
Leaves any dependencies that were installed with it on the system.
Dependencies become orphaned and take up disk space.
remove a package with state: absent and autoremove: yes
Removes the specified package and also removes any dependencies no longer needed by other packages.
Cleans up the system more thoroughly.
Safer when you want to completely remove a software stack.
dnf module with a list of packages
More efficient: one task, one connection, one transaction.
Uses the YAML list syntax for the `name` parameter.
Dependency resolution is done once for all packages in the list.
dnf module with a single package name in a loop
Less efficient: each package is installed in a separate task and transaction.
Requires a `loop` or `with_items` directive.
Can be more readable if you need to install different packages conditionally.
Mistake
Ansible's 'dnf' module can install packages from any URL by directly specifying the .rpm file location.
Correct
The `dnf` module installs from configured repositories only. To install a local .rpm file, you must use the `rpm` module or the `command` module with `dnf localinstall`, which is not idempotent.
Beginners often think a package manager works like a simple downloader, not understanding it relies on a repository index and metadata to resolve dependencies.
Mistake
If I set `state: present`, Ansible will automatically update the package to the latest version every time I run the playbook.
Correct
`state: present` only ensures the package is installed. It will NOT update it if it is already present. To get the latest version, you must use `state: latest`.
This mistake happens because 'present' sounds like 'currently available', but in Ansible terminology it means 'exists, regardless of version'. The nuance between 'present' and 'latest' is one of the most tested distinctions on the exam.
Mistake
Removing a package with `state: absent` automatically removes all its dependencies that were installed with it.
Correct
By default, `state: absent` only removes the specified package. Dependencies remain on the system as orphaned packages. You must explicitly use the `autoremove: yes` option with `state: absent` to clean up unused dependencies.
This matches real-world experience: people assume removal is a clean 'undo', but package managers are conservative to avoid breaking other software that might rely on the same dependency.
Mistake
I can use the `yum_repository` module to create a new repository on the control node itself.
Correct
The `yum_repository` module manages repository files (`/etc/yum.repos.d/`) on the target nodes, not on the control node (the machine running `ansible-playbook`). Ansible modules always act on the remote target, not on localhost unless explicitly told to run locally.
Newcomers often confuse the 'control node' with the 'target node'. They think the playbook modifies the machine they are typing on, but Ansible is fundamentally a remote automation tool.
Mistake
The `dnf` module and the `package` module are completely interchangeable and equally good for automating package management.
Correct
The `package` module is a generic abstraction that works across different OS families (Red Hat, Debian, etc.) but it uses the OS's native package manager (e.g., `dnf` on RHEL, `apt` on Ubuntu). For the EX294 exam, you should use the OS-specific module (`dnf` or `yum`) because it is more reliable and you are specifically tested on Red Hat systems. The `package` module may be slower or have inconsistent behaviour.
This confusion arises from Ansible's versatility. Beginners think 'one module to rule them all' is always better, but for a specific exam targeting RHEL, the specific module is safer and more direct.
Reveal each answer, then mark whether you got it right. Score 60%+ to unlock the next chapter.
The `package` module is a generic abstraction that works across multiple operating systems (like Debian, Red Hat, etc.), automatically choosing the right package manager. The `dnf` module is specific to RHEL/CentOS/Fedora systems. For EX294, you must use the `dnf` module because it is the Red Hat standard and more predictable.
Specify the version in the `name` parameter: `name: httpd-2.4.37`. With `state: present`, it will install that exact version. If `state: latest`, it will upgrade to the latest version regardless of what you specify, so avoid combining `latest` with a version string.
This usually means the package name is misspelled, or the repository containing the package is not enabled on the target system. Check the `yum_repository` tasks or ensure the target machine has a valid subscription to the required Red Hat repositories.
No. The `dnf` module only works with packages from configured repositories. To install a local .rpm file, you must use the `rpm` module or the `command` module with `dnf localinstall /path/to/file.rpm`. However, this is not recommended because it bypasses dependency resolution.
When you remove a package with `state: absent` and set `autoremove: yes`, Ansible will also remove any packages that were automatically installed as dependencies of that package and are no longer needed by any other installed software. This keeps the system clean.
Use the `dnf` module with `name: '*'` and `state: latest`. This tells Ansible to update every installed package to the latest version available in the repositories. Be careful in production as this can cause unexpected changes.
You've finished Software Package Management. Continue through the EX294 study guide to build a complete picture of the exam.
Done with this chapter?