What a package is

A software package is one file that contains everything needed to install a program: the program's files (binaries, libraries, default configuration, documentation, manual pages), metadata (name, version, architecture, a description, and which other packages it needs), and small scripts that run before or after installation or removal — for example to create a system user or to restart a service after an update.

There are two main formats, one per distribution family (see distributions and families):

  • .deb on Debian, Ubuntu and their relatives. Inside is a control archive (metadata and the maintainer scripts preinst, postinst, prerm, postrm) and a data archive with the files.
  • .rpm (originally "Red Hat Package Manager") on Red Hat Enterprise Linux (RHEL), Fedora, Rocky Linux, AlmaLinux and SUSE: a header with the metadata and scriptlets, followed by the files.

A package file name tells you most of the metadata at a glance:

htop_3.3.0-4build1_amd64.deb        name_version-revision_architecture.deb
htop-3.2.2-2.el9.x86_64.rpm         name-version-release.architecture.rpm

amd64 and x86_64 are two names for the same 64-bit PC architecture; arm64 and aarch64 are the 64-bit ARM equivalents, and all / noarch marks a package that runs anywhere (scripts, data). The part after the upstream version (-4build1, -2.el9) is the distribution's own revision: the same upstream version can be rebuilt or patched several times.

Once a package is installed, the package manager records every file it placed. That database is what makes clean removal, verification and "which package owns this file?" possible — and it is the reason to avoid copying programs into system directories by hand.

Repositories and trust

A package repository is a server (or directory) that holds packages together with an index: a list of every package, its version, its dependencies and a checksum. Your system keeps a local copy of the indexes of the repositories it is configured to use, and the package manager searches that copy.

  • Debian and Ubuntu list repositories in /etc/apt/sources.list and /etc/apt/sources.list.d/. Ubuntu 24.04 uses the newer multi-line format in /etc/apt/sources.list.d/ubuntu.sources.
  • RHEL and Fedora use one .repo file per repository in /etc/yum.repos.d/. On RHEL, the official repositories (BaseOS and AppStream) are enabled through the Red Hat subscription.

Signing keys

Packages usually travel over the Internet, sometimes through mirror servers run by universities or companies you have never heard of. What protects you is a digital signature. The distribution signs its repository metadata (apt) or each package (rpm) with a private GNU Privacy Guard (GPG) key; your system has the matching public key and checks every download against it. A package that was changed on the way, or that comes from someone without the private key, fails the check and is refused.

  • apt checks the signature on the repository's InRelease (or Release) file. That file lists checksums of the package indexes, and the indexes list checksums of every package — so one signature protects the whole chain.
  • dnf checks the signature of each package (gpgcheck=1 in the .repo file) and can also check the repository metadata (repo_gpgcheck=1).

The trust you place in a key is total: whoever holds the private key can ship any software to your machine, and the install scripts run as root. That is why adding a repository deserves the care described in third-party repositories below.

Dependencies

A dependency is another package that a package needs in order to work — usually a shared library, sometimes a tool or an interpreter. Instead of every program bringing its own copy of, say, the OpenSSL library, one copy is installed and shared. When a security fix for that library arrives, one update fixes every program that uses it.

The package metadata lists the dependencies with version limits (Depends: libc6 (>= 2.38), libncursesw6 (>= 6)). Installing a package therefore often installs a few others; removing a library that other packages need would remove those too, and the package manager warns you before it does. Packages installed only as a dependency are marked automatically installed, so they can be cleaned up once nothing needs them any more.

Low-level and high-level tools

Each family has two layers of tools. The low-level tool installs one package file that is already on disk and maintains the database; it does not download anything and does not resolve dependencies. The high-level tool talks to the repositories, works out the full set of packages needed, downloads them, and then calls the low-level tool.

Package tools in layers. Repositories on the Internet feed the high-level tool, apt or dnf, which resolves dependencies and downloads packages. It hands the package files to the low-level tool, dpkg or rpm, which installs the files and updates the package database. Repositories signed indexes + packages apt / dnf resolve, download dpkg / rpm install one package Files on disk + package database .deb / .rpm files You normally use the upper tool; the lower one does the actual installing.
apt and dnf work out what to fetch; dpkg and rpm put the files in place and keep the records.
Family Package format Low-level tool High-level tool
Debian, Ubuntu .deb dpkg apt (interactive), apt-get / apt-cache (scripts)
RHEL, Fedora, Rocky, Alma .rpm rpm dnf (yum on old systems)
SUSE, openSUSE .rpm rpm zypper

apt (Advanced Package Tool) is the high-level package manager of Debian and Ubuntu. The apt command is meant for people at a keyboard: it has progress bars and colors, and its output may change between versions. In scripts, use apt-get and apt-cache, whose output is kept stable — which is why apt prints "WARNING: apt does not have a stable CLI interface" when its output goes into a pipe.

dnf (Dandified YUM) is the high-level package manager of Fedora, RHEL and their rebuilds. It replaced yum (Yellowdog Updater, Modified); on RHEL 8 and later the yum command still exists as a link to dnf, so old instructions keep working. Fedora 41 and later ship DNF5, a rewrite with the same everyday commands.

The apt workflow

Every change to installed software needs root, so these commands run with sudo; looking things up does not.

update, upgrade and full-upgrade

apt update downloads the latest indexes from every configured repository. It changes no software at all; it only tells apt what is available.

$ sudo apt update
Hit:1 http://archive.ubuntu.com/ubuntu noble InRelease
Get:2 http://archive.ubuntu.com/ubuntu noble-updates InRelease [126 kB]
Get:3 http://security.ubuntu.com/ubuntu noble-security InRelease [126 kB]
Get:4 http://archive.ubuntu.com/ubuntu noble-updates/main amd64 Packages [1,178 kB]
Fetched 1,430 kB in 2s (715 kB/s)
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
12 packages can be upgraded. Run 'apt list --upgradable' to see them.

Hit means the index had not changed since last time; Get means a newer one was downloaded. noble is the code name of Ubuntu 24.04, and noble-security is the repository with security fixes. Then the actual update:

$ apt list --upgradable
$ sudo apt upgrade
Listing... Done
libssl3t64/noble-updates,noble-security 3.0.13-0ubuntu3.6 amd64 [upgradable from: 3.0.13-0ubuntu3.5]
openssl/noble-updates,noble-security 3.0.13-0ubuntu3.6 amd64 [upgradable from: 3.0.13-0ubuntu3.5]
…
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
Calculating upgrade... Done
The following packages will be upgraded:
  libssl3t64 openssl …
12 upgraded, 0 newly installed, 0 to remove and 0 not upgraded.
Need to get 9,874 kB of archives.
After this operation, 45.1 kB of additional disk space will be used.
Do you want to continue? [Y/n]

Always read that summary before answering Y, in particular the "to remove" count. The three levels differ in what they are allowed to do:

  • apt upgrade installs newer versions of installed packages. It may install new packages when an upgrade needs them, but it never removes an installed package; an upgrade that would need a removal is held back ("kept back").
  • apt full-upgrade (apt-get dist-upgrade in the older tool) may also remove packages when that is the only way to complete the upgrade. It is the normal way to apply updates that change dependencies, but read the list of removals.
  • apt-get upgrade is the most careful: it neither installs new packages nor removes any.

None of these move you to a new release of the distribution (for example Ubuntu 24.04 to 26.04); that is a separate, planned procedure (do-release-upgrade on Ubuntu). Patch policy and timing are covered in Updates, Patches and Drivers.

install, remove, purge and autoremove

$ sudo apt install htop
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
The following additional packages will be installed:
  libnl-genl-3-200
Suggested packages:
  lm-sensors lsof strace
The following NEW packages will be installed:
  htop libnl-genl-3-200
0 upgraded, 2 newly installed, 0 to remove and 12 not upgraded.
Need to get 184 kB of archives.
After this operation, 482 kB of additional disk space will be used.
Do you want to continue? [Y/n]

The dependency (libnl-genl-3-200) is added automatically. Suggested packages are optional extras that are not installed unless you ask for them. -y answers yes in advance, which is useful in scripts but skips your chance to read the summary.

Command Removes program files Removes configuration in /etc Removes unused dependencies
sudo apt remove nginx Yes No No
sudo apt purge nginx Yes Yes No
sudo apt autoremove — No (add --purge) Yes, all that nothing needs any more

remove keeps configuration files on purpose: if you reinstall, your settings come back. purge is for when you want the package really gone, or want to start over with the default configuration. Neither touches data such as databases or websites under /var or users' home directories, as a rule.

search, show and list

$ apt search --names-only '^nginx'
$ apt show nginx
$ apt list --installed
Sorting... Done
Full Text Search... Done
nginx/noble-updates,noble-security 1.24.0-2ubuntu7.4 amd64
  small, powerful, scalable web/proxy server
nginx-common/noble-updates,noble-security 1.24.0-2ubuntu7.4 all
  small, powerful, scalable web/proxy server - common files
…
Package: nginx
Version: 1.24.0-2ubuntu7.4
Priority: optional
Section: web
Origin: Ubuntu
Depends: nginx-common (= 1.24.0-2ubuntu7.4), libc6 (>= 2.34), libcrypt1 (>= 1:4.1.0), libpcre2-8-0 (>= 10.22), libssl3t64 (>= 3.0.0), zlib1g (>= 1:1.2.0)
…
Listing... Done
adduser/noble,now 3.137ubuntu1 all [installed,automatic]
bash/noble,now 5.2.21-2ubuntu4 amd64 [installed]
…

search looks in names and descriptions (so it finds a lot; --names-only narrows it). show prints the metadata, including the dependencies. In the list, [installed,automatic] marks packages that came in as a dependency — the ones autoremove may clean up later.

What dpkg knows

dpkg -l prints the package database directly. The first two letters of each line are the desired state and the actual state: ii is installed, rc is removed with configuration files still present (a package that was removed, not purged), and hi is installed and held. Because the listing is plain text, the filters from Pipes, Redirection and Filters work on it. Here a saved listing is summarized:

cat > packages.txt <<'EOF'
Desired=Unknown/Install/Remove/Purge/Hold
| Status=Not/Inst/Conf-files/Unpacked/halF-conf/Half-inst/trig-aWait/Trig-pend
|/ Err?=(none)/Reinst-required (Status,Err: uppercase=bad)
||/ Name           Version                 Architecture Description
+++-==============-=======================-============-=================================
rc  apache2        2.4.58-1ubuntu8.4       amd64        Apache HTTP Server
ii  bash           5.2.21-2ubuntu4         amd64        GNU Bourne Again SHell
ii  git            1:2.43.0-1ubuntu7.3     amd64        fast, scalable, distributed revision control system
ii  htop           3.3.0-4build1           amd64        interactive processes viewer
ii  nginx          1.24.0-2ubuntu7.4       amd64        small, powerful, scalable web/proxy server
hi  postgresql-16  16.10-0ubuntu0.24.04.1  amd64        object-relational SQL database, version 16 server
ii  tmux           3.4-1ubuntu0.1          amd64        terminal multiplexer
rc  vim-tiny       2:9.1.0016-1ubuntu7.8   amd64        Vi IMproved - enhanced vi editor - compact version
EOF

echo "Packages per state:"
awk 'NR > 5 {print $1}' packages.txt | sort | uniq -c

echo "Held at their current version:"
awk '$1 == "hi" {print "  " $2, $3}' packages.txt

echo "Removed, configuration files left behind:"
awk '$1 == "rc" {print "  " $2}' packages.txt
Packages per state:
      1 hi
      5 ii
      2 rc
Held at their current version:
  postgresql-16 16.10-0ubuntu0.24.04.1
Removed, configuration files left behind:
  apache2
  vim-tiny

NR > 5 skips the five header lines; $1 is the state column and $2 the name. On a real system you would pipe dpkg -l straight into the same awk commands, and the rc list is exactly what a sudo apt purge could clean up. On RHEL, rpm -qa lists the installed packages, one per line as name-version-release.arch (for example nginx-core-1.20.1-22.el9_6.3.x86_64).

apt and dnf side by side

Most everyday tasks map one to one. The main difference in habit: dnf refreshes its metadata by itself when the cached copy is old, so there is no separate "update the lists" step, and dnf upgrade (or its alias dnf update) does everything in one command.

Task Debian / Ubuntu RHEL / Fedora
Refresh the package lists sudo apt update automatic; dnf check-update shows what is new
Install all updates sudo apt upgrade sudo dnf upgrade
Updates that may remove packages sudo apt full-upgrade sudo dnf upgrade (handles obsoleted packages)
Install a package sudo apt install nginx sudo dnf install nginx
Install a downloaded file sudo apt install ./app.deb sudo dnf install ./app.rpm
Remove a package sudo apt remove nginx sudo dnf remove nginx
Remove with configuration sudo apt purge nginx no equivalent; changed configuration files are kept as .rpmsave
Remove unneeded dependencies sudo apt autoremove sudo dnf autoremove
Search apt search nginx dnf search nginx
Show details apt show nginx dnf info nginx
List installed packages apt list --installed dnf list --installed
Which package owns this file? dpkg -S /usr/bin/htop rpm -qf /usr/bin/htop
Which package would provide a file? apt-file search bin/dig (install apt-file first) dnf provides '*/bin/dig'
List a package's files dpkg -L htop rpm -ql htop
History of changes /var/log/apt/history.log dnf history

A typical install on RHEL 9 (trimmed):

$ sudo dnf install nginx
Updating Subscription Management repositories.
Last metadata expiration check: 0:42:10 ago on Wed 30 Sep 2026 09:12:44 AM UTC.
Dependencies resolved.
================================================================================
 Package             Arch    Version              Repository                        Size
================================================================================
Installing:
 nginx               x86_64  2:1.20.1-22.el9_6.3  rhel-9-for-x86_64-appstream-rpms  36 k
Installing dependencies:
 nginx-core          x86_64  2:1.20.1-22.el9_6.3  rhel-9-for-x86_64-appstream-rpms 571 k
 nginx-filesystem    noarch  2:1.20.1-22.el9_6.3  rhel-9-for-x86_64-appstream-rpms 9.6 k
 redhat-logos-httpd  noarch  90.4-2.el9           rhel-9-for-x86_64-appstream-rpms  18 k

Transaction Summary
================================================================================
Install  4 Packages

Total download size: 635 k
Installed size: 1.8 M
Is this ok [y/N]:

Note the default answer: apt's prompt is [Y/n] (Enter means yes), dnf's is [y/N] (Enter means no).

Holding a version

Sometimes one package must stay at its current version — a database server whose upgrade needs a planned migration, or a kernel module that only builds against the current kernel. Holding (apt) or version locking (dnf) excludes it from upgrades until you release it:

$ sudo apt-mark hold postgresql-16
$ apt-mark showhold
$ sudo apt-mark unhold postgresql-16
postgresql-16 set on hold.
postgresql-16
Canceled hold on postgresql-16.

On RHEL the versionlock plugin does the same (sudo dnf versionlock add postgresql-server; on RHEL 9 install python3-dnf-plugin-versionlock first). For more control, apt pinning (priorities in /etc/apt/preferences.d/) decides which repository wins when several offer the same package.

Adding a third-party repository safely

Vendors such as database, monitoring or container companies often publish their own repository so that you get their newest version and their updates through the normal package manager. That is much better than a one-off download: you do get updates. But remember the trust rule — the vendor's key can now install anything as root. Add only repositories from vendors you would trust with root, from their official documentation.

The safe pattern on Debian and Ubuntu stores the vendor's key in its own file and ties it to that one repository with signed-by, so the key cannot vouch for packages from any other repository (the fictional vendor here uses the reserved domain example.com):

$ sudo install -d -m 0755 /etc/apt/keyrings
$ sudo curl -fsSL https://packages.example.com/apt/key.asc -o /etc/apt/keyrings/example.asc
$ gpg --show-keys /etc/apt/keyrings/example.asc
$ echo "deb [signed-by=/etc/apt/keyrings/example.asc] https://packages.example.com/apt stable main" | sudo tee /etc/apt/sources.list.d/example.list
$ sudo apt update
$ sudo apt install example-agent
  1. Create /etc/apt/keyrings, the conventional place for repository keys you add yourself.
  2. Download the vendor's public key over HTTPS (Hypertext Transfer Protocol Secure). -f fails on server errors, -sS hides progress but shows errors, -L follows redirects.
  3. Show the key's fingerprint and compare it with the one the vendor publishes. This is the step that actually establishes trust.
  4. Add the repository line. signed-by= names the key file; stable is the suite and main the component, as the vendor documents them.
  5. Refresh the lists and install.

signed-by limits which key may sign the repository, not what the repository may replace: a vendor repository can still offer a newer version of any package. Pin it (/etc/apt/preferences.d/) if it should only provide its own packages.

On RHEL the vendor supplies a .repo file for /etc/yum.repos.d/; check that it contains gpgcheck=1 and a gpgkey= line pointing to the vendor's key. Extra Packages for Enterprise Linux (EPEL), run by the Fedora project, is the best-known extra repository for RHEL.

Snap, Flatpak and AppImage

Distribution packages share libraries, which keeps systems small and patches simple — but it also means an application can only be as new as the libraries of your release allows. Three formats take the opposite approach: the application brings its dependencies with it and works across distributions.

Snap

A Snap is a self-contained application package from Canonical, the company behind Ubuntu. The snapd service installs snaps from the Snap Store, updates them automatically in the background. Most snaps run confined (sandboxed) with only the access their interfaces allow; "classic" snaps, which the install command must allow explicitly (--classic), have full system access. Snaps work for command-line tools, desktop applications and server software; on Ubuntu some programs (Firefox, for example) are delivered only as a snap.

$ snap find nextcloud
$ sudo snap install nextcloud
$ snap list
$ sudo snap refresh

Each snap is a compressed image that is mounted when the system starts, which is why an Ubuntu machine shows several /dev/loop devices in df and lsblk. That is normal, not a problem.

Flatpak

Flatpak is a cross-distribution format for desktop applications, most often installed from the Flathub repository. Applications are built on shared runtimes (for example a GNOME or KDE runtime) instead of the host's libraries, and run in a sandbox that asks for access to files or devices through portals. Fedora Workstation and many other desktops include it by default.

$ flatpak remote-add --if-not-exists flathub https://dl.flathub.org/repo/flathub.flatpakrepo
$ flatpak install flathub org.gimp.GIMP
$ flatpak update

AppImage

An AppImage is a single executable file that contains the application and its libraries. You download it, make it executable (chmod +x) and run it; nothing is installed and there is no daemon. The flip side: no automatic updates, no sandbox by default, and no repository signature checking what you downloaded — you are back to trusting a download page.

Distribution package Snap Flatpak AppImage
Libraries Shared with the system Bundled Bundled or shared runtimes Bundled
Updates With the system (apt / dnf) Automatic flatpak update or software center Manual
Sandbox No Mostly (confinement; not for "classic" snaps) Yes No, by default
Best for Servers, system tools, anything security-critical Newer apps on Ubuntu, some server apps Desktop apps on any distribution Trying a desktop app without installing
Downsides Versions can be old on long-term support releases Larger, slower first start; one store run by Canonical Larger downloads; desktop only No updates or integration

A reasonable rule on servers: use the distribution's packages first, the vendor's signed repository second, and a snap or container image third.

Installing from source, and where files go

When no package exists at all, you can build a program from its source code, classically with ./configure, make and sudo make install. Treat that as the last resort: the package manager does not know about those files, so they get no security updates, cannot be removed cleanly, and may collide with a package installed later. If you must, keep the default prefix /usr/local (which the package manager never touches) and write down what you built and why.

Where a package puts its files

Because the database records every file, you can always ask where a package installed its parts, and the answer follows the file system layout: programs in /usr/bin, configuration in /etc, documentation in /usr/share/doc, and a systemd unit in /usr/lib/systemd/system for anything that runs as a service.

$ dpkg -L htop
$ dpkg -S /usr/bin/htop
/.
/usr
/usr/bin
/usr/bin/htop
/usr/share
/usr/share/applications
/usr/share/applications/htop.desktop
/usr/share/doc
/usr/share/doc/htop
/usr/share/doc/htop/AUTHORS
/usr/share/doc/htop/README.gz
/usr/share/doc/htop/changelog.Debian.gz
/usr/share/man/man1/htop.1.gz
…
htop: /usr/bin/htop

rpm -ql htop and rpm -qf /usr/bin/htop give the same answers on RHEL, and rpm -qc lists only the configuration files. rpm -V nginx (verify) compares the installed files with the database and reports what has been changed since installation — useful when you suspect someone edited or replaced a system file.