Package Management Across Distros: apt, dnf, zypper, pacman and apk
apt update && apt list --upgradable
Every Linux distribution installs software through a package manager, and they all do the same jobs with different words. This guide is a translation table for those jobs, then the details that matter in practice: where repositories and trust keys live, holding and pinning versions, patching a server safely, finding which package owns a file, and fixing the broken-package and lock errors that every admin meets.
The Debian/Ubuntu (apt, dpkg) examples were run on Ubuntu 24.04. The other managers are shown without output because this machine does not have them: the syntax comes from their documentation and standard usage, so test unfamiliar commands on a throw-away VM first.
The Rosetta table: the same job in every distro
Job Debian, Ubuntu (apt) RHEL, Fedora, Rocky (dnf) openSUSE (zypper) Arch (pacman) Alpine (apk)
refresh the package index apt update dnf makecache (done automatically) zypper refresh pacman -Sy apk update
upgrade everything apt upgrade (or full-upgrade) dnf upgrade zypper update pacman -Syu apk upgrade
install a package apt install nginx dnf install nginx zypper install nginx pacman -S nginx apk add nginx
remove a package apt remove nginx dnf remove nginx zypper remove nginx pacman -R nginx apk del nginx
remove with its config apt purge nginx (config is kept as .rpmsave) zypper remove nginx pacman -Rn nginx apk del nginx
remove unused dependencies apt autoremove dnf autoremove zypper packages --unneeded pacman -Rns / -Qdtq (done by del)
search by name or description apt search web server dnf search web server zypper search web server pacman -Ss web server apk search web server
show details of a package apt show nginx dnf info nginx zypper info nginx pacman -Si nginx apk info nginx
list installed packages apt list --installed dnf list installed zypper search -i pacman -Q apk list --installed
which package owns this file? dpkg -S /usr/bin/ls rpm -qf /usr/bin/ls rpm -qf /usr/bin/ls pacman -Qo /usr/bin/ls apk info --who-owns /bin/ls
list the files of a package dpkg -L nginx rpm -ql nginx rpm -ql nginx pacman -Ql nginx apk info -L nginx
which package provides a command apt-file search bin/dig dnf provides '*/bin/dig' zypper what-provides dig pacman -F dig apk search cmd:dig
list upgradable packages apt list --upgradable dnf check-update zypper list-updates pacman -Qu apk version -l '<'
security updates only unattended-upgrade / pro tools dnf upgrade --security zypper patch --category security (arch-audit) (apk audit tools)
simulate (dry run) apt-get -s upgrade dnf upgrade --assumeno zypper --dry-run update pacman -Syu (reads prompt) apk upgrade --simulate
hold or lock a version apt-mark hold nginx dnf versionlock add nginx zypper addlock nginx IgnorePkg in pacman.conf apk add 'nginx=1.24.0-r0'
undo a change (no history: reinstall old version) dnf history undo N snapper rollback (on btrfs) downgrade from cache apk add package=version
clean the download cache apt clean dnf clean all zypper clean pacman -Sc (no cache by default)
verify installed files debsums -s ; dpkg -V pkg rpm -V pkg rpm -V pkg pacman -Qkk pkg apk verify
is a reboot needed? /var/run/reboot-required exists needs-restarting -r zypper needs-rebooting (uname -r vs installed) (uname -r vs installed)
The low-level tools (dpkg on Debian, rpm on RPM systems) work on single package FILES and the local database; the high-level tools (apt, dnf, zypper) also talk to repositories and solve dependencies. Use the high-level tool to install, the low-level one to ask questions.
apt and dpkg in practice (Debian, Ubuntu, Mint)
$ sudo apt update # refresh the lists of available packages: ALWAYS first
$ apt list --upgradable # what would change
$ sudo apt upgrade # install updates, never removes packages; sudo apt full-upgrade also adds/removes packages when a dependency needs it (this is what apt-get dist-upgrade does)
$ sudo apt install nginx # install; sudo apt install nginx=1.24.0-2ubuntu7 pins a version; --no-install-recommends skips "suggested extras"
$ sudo apt install ./package.deb # a local .deb WITH dependency resolution (dpkg -i does not resolve them)
$ apt-cache policy nginx # installed version, available versions, and which repository each comes from
$ apt-cache depends nginx; apt-cache rdepends nginx # what it needs / what needs it
$ sudo apt remove nginx; sudo apt purge nginx # remove keeps /etc config files, purge deletes them too
$ sudo apt autoremove --purge # packages that were installed as dependencies and are no longer needed
$ apt-mark showmanual | head # what YOU asked for, as opposed to dependencies
$ apt-get -s upgrade # simulate: "Inst" lines show what would be installed, "Remv" what would go. Needs no root
$ sudo apt-get install --reinstall nginx # put the files back (a damaged binary or deleted config)
$ apt changelog nginx | head -20 # the changelog: does this update fix the CVE I care about?
Ask dpkg questions (these ran for real)
$ dpkg -S /usr/bin/ls
coreutils: /usr/bin/ls
$ dpkg -s coreutils | grep -E '^(Package|Status|Priority|Essential|Architecture)'
Package: coreutils
Essential: yes
Status: install ok installed
Priority: required
Architecture: amd64
$ dpkg --print-architecture
amd64
$ apt-cache depends coreutils | head -4
coreutils
PreDepends: libacl1
PreDepends: libattr1
PreDepends: libc6
$ dpkg -L nginx | head # every file the package installed
$ dpkg -l 'linux-image*' | grep ^ii # installed kernels (compare with uname -r)
$ dpkg -V nginx # verify the installed files against the package (no output means unchanged; "5" in the output means the checksum differs)
$ sudo dpkg --configure -a # finish an interrupted installation
$ sudo apt --fix-broken install # repair broken dependencies
Debian version numbers: epoch:upstream-revision
Versions are not plain numbers: 1.10 is NEWER than 1.9, "~" sorts BEFORE everything (1.0~rc1 is older than 1.0), and an epoch (the number before a colon) beats everything else. dpkg --compare-versions answers "is A newer than B" the way apt does:
$ dpkg --compare-versions 1.10 gt 1.9 && echo "1.10 is newer than 1.9"
1.10 is newer than 1.9
$ dpkg --compare-versions 1.0~rc1 lt 1.0 && echo "1.0~rc1 is OLDER than 1.0 (a pre-release)"
1.0~rc1 is OLDER than 1.0 (a pre-release)
$ dpkg --compare-versions 1:1.0 gt 2.0 && echo "1:1.0 beats 2.0: an epoch wins over everything"
1:1.0 beats 2.0: an epoch wins over everything
$ dpkg --compare-versions 2.4.1-3ubuntu1 lt 2.4.1-3ubuntu2 && echo "the revision after the dash breaks ties"
the revision after the dash breaks ties
$ dpkg --compare-versions 5.15.0-91.101 lt 5.15.0-107.117 && echo "kernel 91 < 107: compare number by number, not as text"
kernel 91 < 107: compare number by number, not as text
Never compare versions with sort or string tests in scripts: use dpkg --compare-versions, or sort -V for loose cases (on RPM systems, rpmdev-vercmp).
Repositories, keys and trust
Where the lists come from. On Ubuntu 24.04 the main repository is described in the modern "deb822" format:
$ cat <<'EOF'
> # /etc/apt/sources.list.d/ubuntu.sources
> Types: deb
> URIs: http://archive.ubuntu.com/ubuntu/
> Suites: noble noble-updates noble-backports
> Components: main restricted universe multiverse
> Signed-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg
> EOF
# /etc/apt/sources.list.d/ubuntu.sources
Types: deb
URIs: http://archive.ubuntu.com/ubuntu/
Suites: noble noble-updates noble-backports
Components: main restricted universe multiverse
Signed-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg
Types deb means binary packages; Suites are the release and its update streams; Components are the sections (main = supported free software, universe = community, restricted/multiverse = non-free); Signed-By is the key that must have signed this repository. Older systems use one-line entries in /etc/apt/sources.list: deb [signed-by=/etc/apt/keyrings/vendor.gpg] https://repo.example.com/apt stable main
Add a third-party repository the safe way (the pattern most vendors document):
$ sudo install -d -m 0755 /etc/apt/keyrings
$ curl -fsSL https://repo.example.com/key.gpg | sudo gpg --dearmor -o /etc/apt/keyrings/example.gpg # the repository's signing key, ONLY for this repository
$ sudo chmod a+r /etc/apt/keyrings/example.gpg
$ echo "deb [signed-by=/etc/apt/keyrings/example.gpg] https://repo.example.com/apt stable main" | sudo tee /etc/apt/sources.list.d/example.list
$ sudo apt update
The signed-by option limits that key to that ONE repository. The old way, apt-key add (a key trusted for ALL repositories), is deprecated: a compromised vendor key could then sign "updates" for the whole system. Check the key's fingerprint against the vendor's website before you trust it (gpg --show-keys /etc/apt/keyrings/example.gpg).
RPM systems: repositories are files in /etc/yum.repos.d/ with gpgcheck=1 and a gpgkey= line.
$ cat <<'EOF'
> # /etc/yum.repos.d/example.repo
> [example]
> name=Example repository
> baseurl=https://repo.example.com/el9/$basearch/
> enabled=1
> gpgcheck=1
> repo_gpgcheck=1
> gpgkey=https://repo.example.com/RPM-GPG-KEY-example
> EOF
# /etc/yum.repos.d/example.repo
[example]
name=Example repository
baseurl=https://repo.example.com/el9/$basearch/
enabled=1
gpgcheck=1
repo_gpgcheck=1
gpgkey=https://repo.example.com/RPM-GPG-KEY-example
$ sudo rpm --import https://repo.example.com/RPM-GPG-KEY-example # trust the key (check its fingerprint first)
$ dnf repolist; dnf repolist --all # enabled repositories; every repository
$ sudo dnf config-manager --set-enabled crb # turn on an extra repository (CodeReady/PowerTools); EPEL: sudo dnf install epel-release
$ dnf --disablerepo='*' --enablerepo=example install foo # use just one repository for one command
Trust rules: install software from the distribution's repositories first; add third-party repositories only from the vendor you trust, with the key bound to that repository; never disable signature checks (gpgcheck=0, --allow-unauthenticated, --nogpgcheck) to "make an error go away"; and be wary of "curl ... | sudo bash" installers: download, read, then run.
Holding, pinning and version control
$ sudo apt-mark hold nginx # never upgrade this package (apt-mark unhold nginx to release; apt-mark showhold lists them)
$ sudo dnf versionlock add nginx # the same on RHEL (package python3-dnf-plugin-versionlock); dnf versionlock list
$ sudo zypper addlock nginx # openSUSE
apt pinning prefers or blocks versions and repositories by priority. A pin file per topic in /etc/apt/preferences.d/:
$ cat <<'EOF'
> # /etc/apt/preferences.d/nginx-pin
> Package: nginx*
> Pin: version 1.24.*
> Pin-Priority: 1001
>
> # never take this package from the third-party repository
> Package: *
> Pin: origin repo.example.com
> Pin-Priority: 100
> EOF
# /etc/apt/preferences.d/nginx-pin
Package: nginx*
Pin: version 1.24.*
Pin-Priority: 1001
# never take this package from the third-party repository
Package: *
Pin: origin repo.example.com
Pin-Priority: 100
$ apt-cache policy nginx # shows each candidate with its priority: the highest wins (500 is the default, above 1000 even allows downgrades)
Holds and pins are technical debt: they stop security fixes. Write down why, add a reminder date, and remove them when the reason is gone.
Downgrade (when an update broke something)
$ sudo apt install nginx=1.22.1-9 # apt: name the exact older version (an example version string) (it must still be in a repository or the apt cache: apt-cache policy nginx)
$ sudo dnf downgrade nginx # dnf: one step back; sudo dnf history undo last reverts the whole transaction
$ dnf history; dnf history info 42 # every transaction, what it changed, and who ran it
$ sudo pacman -U /var/cache/pacman/pkg/nginx-1.24.0-1-x86_64.pkg.tar.zst # Arch: install an older package from the cache
dnf history is the nicest rollback story among the common managers: apt keeps a LOG (/var/log/apt/history.log, /var/log/dpkg.log) but cannot undo, so on Debian and Ubuntu snapshot the machine (VM snapshot, LVM, btrfs) before a big upgrade.
Patching a server: a safe routine
1. Know what is installed and what is pending: apt update; apt list --upgradable | dnf check-update
2. Read what a security update fixes: apt changelog PACKAGE | dnf updateinfo info --security | ubuntu-security-status
3. Snapshot or back up (VM snapshot, LVM snapshot, a copy of /etc and the data).
4. Simulate: apt-get -s upgrade | dnf upgrade --assumeno
5. Upgrade (staging first, then production): apt upgrade | dnf upgrade
6. Restart what needs it, then reboot if required:
$ ls /var/run/reboot-required 2>/dev/null && cat /var/run/reboot-required.pkgs # Debian, Ubuntu: a reboot is pending, and which packages asked for it
$ sudo needrestart -r l # which SERVICES still run old libraries (package needrestart)
$ sudo needs-restarting -r; sudo needs-restarting -s # RHEL: reboot needed? / which services to restart
$ uname -r; dpkg -l 'linux-image*' | grep ^ii | awk '{print $3}' # the RUNNING kernel against the INSTALLED ones: a newer one installed but not running = not patched yet
7. Verify: the service answers, the logs are clean, uname -r is the new kernel.
A kernel update does nothing until you boot into it. Library updates need the services restarted. The "security" status commands tell you where you stand:
$ ubuntu-security-status # Ubuntu: how many packages are covered by security updates, which ones are not (and what Ubuntu Pro would add)
$ sudo pro security-status # the same, from the Ubuntu Pro client
$ sudo dnf updateinfo list security # RHEL family: pending security advisories (RHSA numbers)
Unattended upgrades: automatic security patching
$ sudo apt install unattended-upgrades
$ sudo dpkg-reconfigure -plow unattended-upgrades # turn it on
$ sudo unattended-upgrade --dry-run -d | tail -20 # what it WOULD do
$ grep -rE 'Allowed-Origins|Automatic-Reboot|Mail' /etc/apt/apt.conf.d/50unattended-upgrades
$ sudo dnf install dnf-automatic && sudo systemctl enable --now dnf-automatic-install.timer # RHEL equivalent
Automatic security updates are good for most servers. They do not reboot unless you set Automatic-Reboot, and they can still break a service: use them with monitoring, staged rollout (test machines get updates first) and snapshots.
"The following packages have been kept back" means an upgrade needs NEW packages or removals that apt upgrade will not do: look at what it wants with apt full-upgrade -s, then install that package explicitly (apt install PACKAGE) or run apt full-upgrade. Ubuntu also stages some updates in phases, so a machine may see an update a few days after another.
Questions about files and commands
$ dpkg -S /usr/sbin/nginx # which package owns this file (installed packages only)
$ apt-file update && apt-file search bin/dig # which package PROVIDES a file, even if it is not installed (package apt-file)
$ command-not-found dig # Ubuntu suggests the package when you type a missing command
$ dnf provides '*/bin/dig'; dnf provides /etc/ssh/sshd_config
$ rpm -qf /etc/ssh/sshd_config; rpm -qi openssh-server; rpm -ql openssh-server | head
$ rpm -qa --last | head # the most recently installed packages
$ rpm -V openssh-server # verify files against the package: S size, 5 checksum, T time, M mode, U owner changed. "c" marks config files
$ rpm -qp --scripts package.rpm # what the install scripts of a downloaded package would run (read before installing)
$ rpm2cpio package.rpm | cpio -idmv # unpack an rpm without installing it; dpkg-deb -x package.deb outdir for a deb
$ apt download nginx; dnf download nginx # fetch the package file only
Config files you edited: on upgrade apt asks "keep your version or take the maintainer's?" (answer with care; the other version is saved as .dpkg-old or .dpkg-dist). RPM saves yours as .rpmsave or the new one as .rpmnew: look for them after an upgrade, they are where changed defaults hide.
$ sudo find /etc -name '*.dpkg-*' -o -name '*.rpmnew' -o -name '*.rpmsave' -o -name '*.ucf-*' 2>/dev/null
Other formats and language managers
snap, flatpak and AppImage bundle an application with its libraries.
$ snap list; sudo snap refresh; snap info firefox # snap: confined, auto-updating packages (Ubuntu ships some by default)
$ flatpak list; flatpak update # flatpak: desktop applications
Language package managers (pip, npm, gem, cargo, go install) install into THEIR OWN places. Do not mix them with the system packages:
$ python3 -m venv ~/venvs/myapp && ~/venvs/myapp/bin/pip install requests # a virtual environment: the safe way to use pip
$ pipx install httpie # install a Python command-line tool in its own environment
$ sudo pip install requests # "error: externally-managed-environment": the distribution protects its Python (PEP 668). Use a venv or pipx; do NOT add --break-system-packages on a server
Containers are the other answer for software that needs its own versions: see the Docker guides.
When it breaks
Symptom Cause and fix
"Could not get lock /var/lib/dpkg/lock-frontend" another apt/dpkg (unattended-upgrades, a GUI updater) is running: wait, or ps aux | grep -E 'apt|dpkg'. Delete lock files ONLY when you are SURE nothing runs (a crashed apt), then sudo dpkg --configure -a
"E: dpkg was interrupted ... run dpkg --configure -a" an install was cut off: sudo dpkg --configure -a, then sudo apt --fix-broken install
"E: Unable to locate package X" you forgot apt update, the package is in a repository that is not enabled (universe, a third-party repo), or the name differs on this release: apt-cache search KEYWORD
"NO_PUBKEY ABCD1234" or "The following signatures couldn't be verified" the repository key is missing or expired: fetch the vendor's CURRENT key (see above), never apt-key adv --recv-keys from a random server
"Hash Sum mismatch", "File has unexpected size" a mirror is mid-sync or a proxy cached an old index: sudo apt clean; sudo rm -rf /var/lib/apt/lists/*; sudo apt update
"unmet dependencies", "held broken packages" a hold, a pin or a mixed-release repository: apt-cache policy PACKAGE; remove the hold; never mix releases (noble packages on jammy)
"No space left on device" during upgrade df -h / /boot /var: clean the cache (apt clean), remove old kernels (sudo apt autoremove --purge), then sudo dpkg --configure -a
dnf: "Failed to download metadata for repo" network or proxy, or the repository moved: dnf clean all; dnf makecache -v; check baseurl or mirrorlist
dnf: "Error: Transaction test error ... conflicts" a file collision between packages: read both names; dnf remove the old one; never rpm -i --force
rpm: "error: db5 error(-30973)" or a stuck database sudo rm -f /var/lib/rpm/__db*; sudo rpm --rebuilddb
Package installed but the command is not found the package puts it in /usr/sbin or /opt: dpkg -L PACKAGE | grep bin; hash -r; or the shell needs a new login
Services broke after an upgrade journalctl -u SERVICE; look for .dpkg-dist/.rpmnew config files; downgrade or restore the snapshot; read the changelog (apt changelog / rpm -q --changelog)
Old kernels filling /boot sudo apt autoremove --purge; dnf: sudo dnf remove $(dnf repoquery --installonly --latest-limit=-2 -q) (keeps the newest two)
Housekeeping
$ sudo du -sh /var/cache/apt /var/lib/apt/lists /var/cache/dnf 2>/dev/null
$ sudo apt clean; sudo apt autoremove --purge; sudo dnf clean all; sudo dnf autoremove
$ dpkg -l | awk '/^rc/ {print $2}' # packages removed but with config left behind: sudo apt purge $(dpkg -l | awk '/^rc/ {print $2}')
$ dpkg-query -W -f='${Installed-Size}\t${Package}\n' | sort -rn | head -15 # the biggest installed packages (KiB); rpm -qa --qf '%{SIZE}\t%{NAME}\n' | sort -rn | head
$ apt list --installed 2>/dev/null | wc -l; dpkg-query -W | wc -l # a quick inventory count (a baseline for audits)
$ dpkg-query -W -f='${Package} ${Version}\n' > packages-$(hostname -s).txt # a package list you can diff between machines or over time: diff <(sort a.txt) <(sort b.txt)
Tips
apt update first, always. Simulate (-s, --assumeno) before large upgrades, and snapshot first on important machines.
Patch means upgrade AND restart: a kernel needs a reboot, libraries need service restarts (needrestart, needs-restarting).
Use the distribution's repositories; bind each third-party key to its own repository (signed-by); never turn off signature checks.
Holds and pins stop security updates: write down why and review them.
Keep Python, Node and friends in virtual environments or containers, not in the system's site-packages.
Learn dpkg -S / rpm -qf, dpkg -L / rpm -ql and apt-file / dnf provides: "which package is this?" is the question you ask most.
Know your tool's undo: dnf history undo, snapshots, or a downgrade command you have already tested.
"Works on my machine" is often a package version. Print versions (dpkg-query -W) into your tickets.