Table of contents
- Why “Package Management” Exists as a Separate Concept
- Debian/Ubuntu Family — apt
- RHEL Family — dnf / yum
- Common Concepts — Version Pinning, Caching, and Rollback
- Alternatives — When System Packages Aren’t Enough
- Practical Checklist
Why “Package Management” Exists as a Separate Concept
When you first start with Linux, it’s slightly surprising that most software is installed with a single apt install command, unlike Windows exe installers. In fact, the Linux package management system resembles an “app store” — except it’s CLI-based, installing from a central repository while automatically resolving dependencies.
Let’s start with why a separate concept is needed. Linux applications generally depend on shared libraries. For example, curl uses OpenSSL, and Python uses libffi, readline, and libsqlite3. These dependency graphs can expand to hundreds of nodes. Manually installing each one quickly leads to version conflicts and collisions. A package manager is a tool that resolves the dependency graph and installs everything at once.
Here’s the big picture.
flowchart LR
USER["User<br/>(apt install nginx)"] --> MGR["Package Manager<br/>(apt / dnf)"]
MGR --> CACHE["Local metadata cache"]
MGR -->|"HTTPS"| REPO["Repository<br/>(archive.ubuntu.com, etc.)"]
REPO --> PKGS["Package index<br/>& .deb / .rpm files"]
MGR --> DB[("Installation state DB<br/>/var/lib/dpkg/ or /var/lib/rpm/")]
MGR --> FS["Install to filesystem<br/>/usr/bin, /etc, /lib"]
When the user types a command, the package manager fetches metadata from the repository, calculates the dependency graph, downloads the needed .deb/.rpm files, extracts them, and records the installation state in a database. The details differ by distribution, but the skeleton is the same.
Debian/Ubuntu Family — apt
Debian and its derivatives (Ubuntu, Mint, Pop!_OS, etc.) use the .deb format and apt tools. Let’s first run through the most frequently used commands.
# Update repository metadata (almost always run before installing)
sudo apt update
# Check which packages can be upgraded
apt list --upgradable
# Upgrade all packages
sudo apt upgrade
# Install a package
sudo apt install nginx
# Install a specific version
sudo apt install nginx=1.24.0-1ubuntu1
# Remove (keeps configuration)
sudo apt remove nginx
# Remove + delete configuration files too
sudo apt purge nginx
# Clean up auto-installed dependencies no longer needed
sudo apt autoremove
# Search by name/description
apt search nginx
# Detailed info on an installed package
apt show nginx
# Which package did this file come from?
dpkg -S /usr/sbin/nginx
# List files installed by a specific package
dpkg -L nginx
The difference between apt update and apt upgrade can be confusing, but it’s easy to remember as a pair. update is “refresh the index”, upgrade is “actually install.” The fact that “update only refreshes the list and doesn’t install anything” is counterintuitive at first.
apt-get vs apt — Why Are There Two?
In field documentation, apt-get install and apt install appear interchangeably. They do the same thing, but their history and use cases differ.
apt-get,apt-cache: Older, lower-level tools. Stable for scripts and CI. Their core philosophy is that options and output formats don’t changeapt: A user-friendly frontend introduced in 2014. It has progress bars, colors, and helper messages. More convenient when a human is typing at a terminal
Use apt-get in scripts, use apt for interactive use — this is the convention. When you use apt install in a Dockerfile, you’ll see a warning about “the CLI being user-facing with an unstable format,” and this is why.
# Recommended in Dockerfiles
RUN apt-get update && apt-get install -y --no-install-recommends nginx
Where Are Repositories Defined?
We said apt “fetches from repositories,” but where are those repository lists written? In /etc/apt/sources.list and /etc/apt/sources.list.d/*.list.
cat /etc/apt/sources.list
# deb http://archive.ubuntu.com/ubuntu jammy main restricted universe multiverse
# deb http://archive.ubuntu.com/ubuntu jammy-updates main restricted universe multiverse
# deb http://security.ubuntu.com/ubuntu jammy-security main restricted universe multiverse
Breaking down what one line means:
deb: Binary package repository (deb-srcis for source packages)http://archive.ubuntu.com/ubuntu: Repository URL (host + path)jammy: Ubuntu 22.04’s release codenamemain/restricted/universe/multiverse: Components with different licensing and support scopes
When adding an external repository (e.g., Docker’s official repo), you create a separate .list file and register the GPG key in the keyring. This is the modern Debian recommended pattern.
# Example: Adding Docker's official repo
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg \
| sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \
https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" \
| sudo tee /etc/apt/sources.list.d/docker.list
sudo apt update
sudo apt install docker-ce docker-ce-cli containerd.io
signed-by=... is the key part. It means “only trust packages from this repository that are signed with this public key.” Previously, keys were added to a global keyring with apt-key add, but due to security concerns, the approach has shifted to isolating keyrings per repository.
When Dependency Resolution Breaks — Held broken packages
Occasionally a package installation fails with “The following packages have unmet dependencies.” Remembering a few recovery commands speeds up your rescue efforts significantly.
# Force repair by reinstalling problematic packages
sudo apt --fix-broken install
# Repair installation state DB consistency
sudo dpkg --configure -a
# Clear cache and retry
sudo rm -rf /var/lib/apt/lists/*
sudo apt update
--fix-broken is roughly “try to sort it out yourself,” and dpkg --configure -a finishes any interrupted installation steps. Try them in order.
RHEL Family — dnf / yum
RHEL (Red Hat Enterprise Linux) and its derivatives (CentOS, Rocky Linux, AlmaLinux, Fedora) use the .rpm format with dnf (or the older yum).
| Distribution | Recommended tool |
|---|---|
| RHEL 8+ / Rocky / Alma / Fedora | dnf |
| CentOS 7 / RHEL 7 (nearing EOL) | yum |
yum is the older Python 2-based version, and dnf is its refactored successor. The interface is nearly identical — most commands can be swapped from yum to dnf and still work. On modern RHEL-based systems, yum is effectively a symlink to dnf.
Here are the most frequently used commands, compared side by side with apt.
| Task | apt | dnf |
|---|---|---|
| Update metadata | apt update | (automatic, or dnf check-update) |
| Upgrade | apt upgrade | dnf upgrade |
| Install | apt install pkg | dnf install pkg |
| Remove | apt remove pkg | dnf remove pkg |
| Search | apt search pkg | dnf search pkg |
| Info | apt show pkg | dnf info pkg |
| File -> package | dpkg -S file | dnf provides file |
| File list | dpkg -L pkg | rpm -ql pkg |
One useful dnf-specific feature is transaction history. This feature lets you precisely roll back “the package installed a few days ago that broke something.”
sudo dnf history
# ID | Command line | Date and time | Action(s) | Altered
# ---+-------------------------+------------------+------------+--------
# 42 | install nginx | 2026-04-18 10:00 | Install | 3
# 41 | upgrade | 2026-04-17 03:00 | E, I, U | 42
# Details of transaction 42
sudo dnf history info 42
# Undo transaction 42
sudo dnf history undo 42
apt has /var/log/apt/history.log, but dnf’s “undo” capability for rolling back in one go is more polished. You can consider RHEL-based systems better equipped for rollback capabilities.
Repository Definition Files
dnf repositories are stored as individual files under /etc/yum.repos.d/*.repo.
# /etc/yum.repos.d/docker-ce.repo
[docker-ce-stable]
name=Docker CE Stable - $basearch
baseurl=https://download.docker.com/linux/centos/$releasever/$basearch/stable
enabled=1
gpgcheck=1
gpgkey=https://download.docker.com/linux/centos/gpg
Variables like $basearch and $releasever are automatically populated by dnf. A notable difference from apt is that repository blocks are organized as INI sections within a single file.
To view enabled/disabled repositories:
dnf repolist --all
To enable a disabled repository for a one-time installation, use --enablerepo.
sudo dnf --enablerepo=epel install htop
Common Concepts — Version Pinning, Caching, and Rollback
Even across different distributions, the shared challenge of “a system with dependencies” is addressed. Here are some common patterns frequently used in practice.
Version Pinning — Locking to a Specific Version
There are cases in CI/deployment environments where “always the same version” matters.
# apt
sudo apt install nginx=1.24.0-1ubuntu1
sudo apt-mark hold nginx # Exclude from upgrade targets
sudo apt-mark unhold nginx
# dnf
sudo dnf install nginx-1.24.0
sudo dnf versionlock add nginx # Plugin required: dnf-plugin-versionlock
Download Without Installing
Useful for transferring to environments with limited network access.
# apt — goes to /var/cache/apt/archives
sudo apt install --download-only nginx
# dnf — to the current directory
sudo dnf download nginx
Apply Security Updates Only
There are times when you hesitate to do a general upgrade on a production server but must apply security patches.
# Ubuntu (manual alternative to unattended-upgrades)
sudo apt install unattended-upgrades
sudo unattended-upgrade --dry-run -d
# RHEL
sudo dnf upgrade --security
sudo dnf upgrade-minimal --security
Alternatives — When System Packages Aren’t Enough
Let’s also briefly touch on three common alternatives used when the distribution repository doesn’t have the tool you want or you need a newer version.
- Snap / Flatpak: Sandboxed packages closer to desktop apps. Ubuntu uses Snap heavily, while Fedora pushes Flatpak by default. Dependencies are bundled inside the package, causing fewer conflicts with the system
- Language-specific package managers:
pip(Python),npm(Node.js),gem(Ruby),cargo(Rust),go install(Go), etc. They isolate tools at the language level. Overwriting OS system tools withpip installis not recommended — it can conflict with versions the distribution uses internally - Official binary releases: Manual installation via
curl ... | bashor.tar.gz. There’s no automatic updates, so you place them in paths like/usr/local/binand manage them yourself
In practice, the safe order is: system package manager (apt/dnf) first, then language manager, manual installation as a last resort. System packages are easier to manage and easier to receive automatic security patches.
Practical Checklist
When I land on a server and install a new tool, the sequence I actually follow roughly looks like this.
flowchart TB
A["1. Check the distribution<br/>cat /etc/os-release"] --> B{"apt? dnf?"}
B --> C["2. Update repository index<br/>apt update or dnf check-update"]
C --> D["3. Search and check info<br/>apt search / dnf search"]
D --> E["4. Decide on version and install"]
E --> F["5. Check file paths<br/>dpkg -L / rpm -ql"]
F --> G["6. systemctl enable --now service"]
G --> H["7. Check startup logs with journalctl"]
Once this sequence becomes second nature, you won’t get stuck wondering “what do I do first?” when landing on a new server. Combined with Part 6’s systemd framework and Part 8’s shell scripts, you’ll have the fundamentals to set up a single server into an operational state.
In the next part, we’ll cover Bash scripting. How to bundle the commands you’ve been typing into a single file for automation, conditionals, loops, functions, exit codes, safety options like set -e and set -u, and practical backup and monitoring script examples.

Loading comments...