Linux Package Management with apt and dnf
You will install, query, pin, and add repositories for software using apt on Debian/Ubuntu and dnf on RHEL/Fedora. By the end you can keep servers patched, audit what is installed, and add third-party repos safely.
Learning Objectives
- Install, remove, and search packages on both package managers.
- Add a third-party repository with a verified signing key.
- Pin a package version and audit installed software.
- Time: ~2 hours · Difficulty: Beginner · Prereqs: a Debian-family and/or RHEL-family server.
Architecture Overview
Environment Setup
You will need: a server with apt or dnf and internet access.
Before you begin: know which family you are on (cat /etc/os-release).
Prerequisites
- A Debian/Ubuntu host for apt and/or a RHEL/Fedora host for dnf, with internet access.
- Root or sudo access for installs and repo changes.
- Know your distro family:
cat /etc/os-release. - No extra tools needed; apt and dnf are preinstalled.
Step-by-Step Execution
apt resolves dependencies automatically; searching helps find the right package name.
sudo apt update && apt search nginx && sudo apt install -y nginxExpected output:
Setting up nginx (1.24.0-2ubuntu7) ...
Processing triggers for man-db ...
(nginx installed)
What this teaches: apt update refreshes metadata before any install; search finds the right package name; -y automates confirmation for scripts.
sudo dnf install -y nginx && dnf info nginx
Expected output:
Installed:
nginx-1:1.24.0-1.el9.x86_64
Complete!
What this teaches: dnf is the RHEL-family counterpart: install resolves dependencies identically, and info shows version, repo, and description.
Verifying the signing key is what makes adding an external repo safe rather than risky.
curl -fsSL https://example.com/key.gpg | sudo gpg --dearmor -o /usr/share/keyrings/example.gpg
Expected output:
(key imported silently; verify with: ls -l /usr/share/keyrings/example.gpg)
What this teaches: Dearmoring the key into /usr/share/keyrings and referencing it with signed-by keeps the repo trust scoped instead of global. Importing the key alone does not add the repo; the sources entry comes next.
echo "deb [arch=amd64 signed-by=/usr/share/keyrings/example.gpg] https://example.com/apt stable main" | sudo tee /etc/apt/sources.list.d/example.list && sudo apt updateExpected output:
deb [arch=amd64 signed-by=/usr/share/keyrings/example.gpg] https://example.com/apt stable main
Hit:1 https://example.com/apt stable InRelease
What this teaches: The deb line binds the repo URL to the key you just imported via signed-by, so a compromised third-party key can only sign this one repo. On RHEL-family systems the equivalent is a .repo file under /etc/yum.repos.d/ with gpgcheck=1 and a gpgkey= URL.
Expected output:
What this teaches: Listing installed packages and counting them turns 'what is on this box' from a guess into an auditable number you can diff over time. Note: the count includes apt list's 'Listing... Done' header line; dpkg-query -W -f='${binary:Package}\n' | wc -l gives a clean count with no header.
Pinning holds a critical package at its current version across upgrades, so unattended updates cannot move it unexpectedly.
sudo apt-mark hold nginx && apt-mark showholdExpected output:
nginx set on hold.
nginx
What this teaches: apt-mark hold is the quick freeze; for version-specific pins, write a Package/Pin/Pin-Priority stanza in /etc/apt/preferences.d/ instead. On RHEL the equivalent is sudo dnf versionlock add nginx (from the dnf-plugins-core package). Release with apt-mark unhold when you are ready to move.
How It Works
Package managers solve dependency resolution: given a desired package, they compute the closure of required libraries, fetch consistent versions from signed repositories, and install them transactionally. apt (with dpkg underneath) and dnf (with rpm underneath) differ in commands but share this model, which is why skills transfer between families.
Repository trust is cryptographic. Each repo signs its metadata with a GPG key; the manager verifies signatures before trusting anything. Adding a third-party repo therefore means adding its key, and the modern practice scopes that key to one repo via signed-by instead of the old global apt-key trust-everything approach.
Pinning and auditing are the fleet disciplines. Pinning (apt preferences, dnf versionlock) holds critical packages at known versions across upgrades; auditing (apt list --installed, rpm -qa) inventories the system so vulnerability scans and compliance checks have ground truth.
Progress So Far
Testing & Validation
nginx -v && systemctl is-enabled nginxYou should see the installed version and that the service is enabled. If so, package management is working end to end.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| NO_PUBKEY errors on apt update | Repo key missing from the keyring | Import the key to /usr/share/keyrings and reference it with signed-by |
| Held or broken packages block upgrades | Conflicting version constraints | Run apt --fix-broken install, then review holds with apt-mark showhold |
| dnf reports nothing provides X | Repo not enabled or package named differently | Enable EPEL/CRB as needed and search with dnf provides |
| Wrong version installed after adding a repo | Repo priority lower than the base | Pin with apt preferences or dnf versionlock to control selection |
| apt update is very slow | Slow or distant mirror | Switch mirrors via software-properties or edit sources.list |
| Accidental dist-upgrade breakage | Major release jump without preparation | Snapshot first, read release notes, prefer full-upgrade deliberately |
Troubleshooting
- NO_PUBKEY error: import the repo's signing key into a keyring before updating.
- Held/broken packages: run
sudo apt --fix-broken install. - Wrong version installed: pin with apt preferences or
dnf versionlock.
Extension Ideas
- Automate patching with unattended-upgrades.
- Build a local mirror or caching proxy (apt-cacher-ng) for many hosts.
- Compare native packages with Snap/Flatpak trade-offs.
Key Results
- Installed and verified software on both apt and dnf systems.
- Added a third-party repo with a verified GPG key, no insecure trust.
- Audited 700+ installed packages for review.
- Established version pinning to prevent unwanted upgrades.
Key Takeaways
- apt update before apt install; dnf mirrors the workflow on RHEL.
- Scope third-party repo keys with signed-by, never global trust.
- Pin critical packages to survive unattended upgrades.
- Audit installed packages regularly; inventory is security ground truth.
- Search before installing; the right package name is half the battle.
Practice Challenges
Challenge 1: Repo add drill
Goal: add a real third-party repo (e.g. Docker or NodeSource) with its key properly scoped, install one package, then remove both cleanly.
Hint: signed-by in the sources line.
Challenge 2: Version hold
Goal: pin nginx to its current version, run a full upgrade, and prove nginx did not move.
Hint: apt-mark hold versus a preferences file; dnf versionlock on RHEL.