Introduction
Ansible has platform-specific package modules (apt, yum, dnf) and a generic package module. Choosing the right one depends on whether your playbooks target a single OS family or multiple distributions. This guide compares all four with cross-platform patterns.
Quick Comparison
| Module | OS Family | Package Manager | Specific Features |
|---|---|---|---|
apt | Debian/Ubuntu | apt/dpkg | update_cache, deb, dpkg_options, PPA |
yum | RHEL/CentOS 7 | yum | enablerepo, disablerepo, security |
dnf | RHEL 8+/Fedora | dnf | allowerasing, nobest, modules |
package | Any | Auto-detect | Only name + state (lowest common denominator) |
apt — Debian/Ubuntu
- name: Update cache and install packages
ansible.builtin.apt:
name:
- nginx
- python3
- redis-server
state: present
update_cache: true
cache_valid_time: 3600 # Don't update if < 1 hour old
- name: Install specific version
ansible.builtin.apt:
name: nginx=1.24.0-1ubuntu1
state: present
- name: Install from .deb file
ansible.builtin.apt:
deb: /tmp/custom-package.deb
- name: Remove package and purge config
ansible.builtin.apt:
name: apache2
state: absent
purge: true
- name: Full system upgrade
ansible.builtin.apt:
upgrade: dist
update_cache: true
yum / dnf — RHEL/CentOS/Fedora
# yum (RHEL 7 / CentOS 7)
- name: Install packages
ansible.builtin.yum:
name:
- nginx
- python3
- redis
state: present
- name: Install from specific repo
ansible.builtin.yum:
name: nginx
enablerepo: epel
state: present
# dnf (RHEL 8+, Fedora, CentOS Stream)
- name: Install packages
ansible.builtin.dnf:
name:
- nginx
- python3
- redis
state: present
- name: Enable module stream
ansible.builtin.dnf:
name: "@postgresql:16/server"
state: present
- name: Install from local RPM
ansible.builtin.dnf:
name: /tmp/custom-package.rpm
state: present
disable_gpg_check: true
package — Generic Cross-Platform
# Works on any distro — detects package manager automatically
- name: Install packages
ansible.builtin.package:
name:
- git
- curl
- wget
state: present
# ⚠️ Package names must be the same across distros
# This works for git, curl, vim — but NOT for everything
package Limitations
# ❌ package doesn't support distro-specific features
- ansible.builtin.package:
name: nginx
update_cache: true # ERROR — not supported
enablerepo: epel # ERROR — not supported
# ❌ Package names differ across distros
# Debian: python3-dev RHEL: python3-devel
# Debian: libssl-dev RHEL: openssl-devel
# Debian: redis-server RHEL: redis
Cross-Platform Patterns
Pattern 1: OS-Specific Variables
# vars/Debian.yml
packages:
- nginx
- python3-dev
- libssl-dev
- redis-server
# vars/RedHat.yml
packages:
- nginx
- python3-devel
- openssl-devel
- redis
# tasks/main.yml
- name: Load OS-specific variables
ansible.builtin.include_vars: "{{ ansible_os_family }}.yml"
- name: Install packages
ansible.builtin.package:
name: "{{ packages }}"
state: present
Pattern 2: Conditional Tasks
- name: Install on Debian/Ubuntu
ansible.builtin.apt:
name: "{{ debian_packages }}"
state: present
update_cache: true
cache_valid_time: 3600
when: ansible_os_family == "Debian"
- name: Install on RHEL/CentOS
ansible.builtin.dnf:
name: "{{ redhat_packages }}"
state: present
when: ansible_os_family == "RedHat"
Pattern 3: Role with OS Support
# roles/webserver/tasks/main.yml
- name: Include OS-specific tasks
ansible.builtin.include_tasks: "{{ ansible_os_family | lower }}.yml"
# roles/webserver/tasks/debian.yml
- ansible.builtin.apt:
name: [nginx, python3-certbot-nginx]
update_cache: true
# roles/webserver/tasks/redhat.yml
- ansible.builtin.dnf:
name: [nginx, python3-certbot-nginx]
enablerepo: epel
Performance Tips
# ✅ Pass a list (single transaction)
- ansible.builtin.apt:
name: [nginx, redis, python3]
state: present
# 1 apt call
# ❌ Loop (separate transaction per package — slow!)
- ansible.builtin.apt:
name: "{{ item }}"
state: present
loop: [nginx, redis, python3]
# 3 apt calls
Common Mistakes
# ❌ Using package for everything (loses features)
- ansible.builtin.package:
name: nginx
# Can't update cache, enable repos, or use distro features
# ✅ Use package for simple cases, apt/dnf for advanced
- ansible.builtin.apt:
name: nginx
update_cache: true # Only with apt
# ❌ Forgetting update_cache on first run
- ansible.builtin.apt:
name: some-new-package
# May fail on fresh server with empty cache!
# ✅ Always update cache (with time limit)
- ansible.builtin.apt:
name: some-new-package
update_cache: true
cache_valid_time: 3600
# ❌ Using yum on RHEL 8+ (it's a dnf alias but less correct)
# ✅ Use dnf for RHEL 8+, Fedora, CentOS Stream
Related Articles
Conclusion
apt for Debian/Ubuntu (richest feature set: cache control, PPA, purge). dnf for RHEL 8+/Fedora (module streams, repo control). yum for RHEL 7 only. package for truly cross-platform playbooks where you only need name + state. For production: use OS-specific variables (include_vars) with the native package module — you get full features without duplicating task logic.