Package Management Modules
Every OS-conditional example so far in this course used debug standing in for “this is where you’d actually install a package” — a fair simplification at the time, but overdue for the real thing. This is where that placeholder finally gets replaced.
The apt Module
Manages packages on Debian-family systems — our ubuntu lab host, specifically:
- name: Install curl
ansible.builtin.apt:
name: curl
state: presentstate: present ensures the package is installed — genuinely idempotent, unlike command/shell: run this again and it correctly reports ok, not changed, if curl is already there.
The dnf Module
Same idea, for RedHat-family systems — our fedora host:
- name: Install curl
ansible.builtin.dnf:
name: curl
state: presentIdentical parameter shape, tied to a different underlying package manager.
The Generic package Module
package abstracts over apt, dnf, and several other package managers, automatically using whichever one is correct for the host it’s running against — no os_family check needed at all:
- name: Install curl (works on both Debian and RedHat family)
ansible.builtin.package:
name: curl
state: presentThis single task runs correctly against both ubuntu and fedora, with zero conditional logic — a much cleaner resolution to the OS-branching problem than manually checking os_family every time, for the cases where it applies. It has a real limit, though, covered directly below.
Native List Support: Installing Several Packages At Once
This is the exact payoff promised — but never actually demonstrated with a real module — back at the close of the loops section:
- name: Install several packages in one operation
ansible.builtin.package:
name:
- curl
- git
- vim
state: presentOne single package-manager invocation, covering all three — not three separate ones looped by hand.
Removing And Updating Packages
- name: Remove a package
ansible.builtin.package:
name: old-tool
state: absent
- name: Upgrade to the latest available version
ansible.builtin.package:
name: curl
state: latestA Realistic OS-Conditional Example, For Real This Time
Here’s the honest limit of package’s abstraction: it knows which manager to use, but it has no idea that the same piece of software might be named differently across distributions — Apache is apache2 on Debian, httpd on RedHat. That naming difference genuinely still needs explicit branching:
- name: Install packages
hosts: servers
tasks:
- name: "Install Apache on Debian-family (package name: apache2)"
ansible.builtin.apt:
name: apache2
state: present
when: ansible_facts['os_family'] == 'Debian'
- name: "Install Apache on RedHat-family (package name: httpd)"
ansible.builtin.dnf:
name: httpd
state: present
when: ansible_facts['os_family'] == 'RedHat'This is the real, working version of the exact pattern taught with debug earlier in this course — same conditional structure, now doing genuine work.
A Gotcha: update_cache And Why It Matters
apt relies on a local package cache to know what’s actually available and installable — and by default, it doesn’t refresh that cache automatically before installing something. A package that’s genuinely available, but wasn’t in the last cache refresh, can fail with a “no candidate” style error even though it would install fine with an up-to-date cache. This bites hardest on freshly-provisioned hosts, or ones that haven’t been touched in a while.
- name: Install a package, refreshing the cache first
ansible.builtin.apt:
name: curl
state: present
update_cache: trueWorth using on the first package-related task in a play, especially against hosts you don’t manage constantly — but not worth adding unconditionally to every package task, since refreshing the cache repeatedly slows things down for no benefit once it’s already current.
Best Practices
- Use
apt/dnfdirectly when you need distribution-specific behavior or package naming differences — the Apache example above is exactly that case. - Use the generic
packagemodule when the name and desired state are genuinely identical across distributions — it’s simpler and avoids unnecessary conditional logic entirely. - Take advantage of native list support for installing multiple packages — this is precisely the efficiency argument from the end of the loops section, now put into real practice.
- Use
update_cache: trueon the first package task in a play, not on every single one, to avoid stale-cache failures without paying the refresh cost repeatedly.