apt and dpkg
Topic 36

apt and dpkg

Tooling

On Debian and Ubuntu, dpkg is the low-level tool that installs a single .deb file and the files inside it. It maintains the package status database at /var/lib/dpkg/status, but it does not resolve dependencies and it never touches the network. apt sits on top of dpkg: it reads repository metadata, computes the full dependency tree, downloads the packages, and then calls dpkg to do the actual unpacking and configuration.

That split decides which tool you reach for. For everyday work — installing, upgrading, removing — you stay in apt, because it handles dependencies and downloads. When an install fails halfway and the database is left half-configured, apt can no longer reason about the state, so you drop to dpkg to inspect or repair it. Knowing which layer you are on tells you whether your problem is metadata and resolution (apt) or the on-disk package state (dpkg).

The Two Layers

dpkg operates only on local files. dpkg -i pkg.deb unpacks and configures one .deb that is already on disk; if that package needs another that is not installed, dpkg writes a half-installed entry and exits with an error. It has no concept of a repository, a candidate version, or a download.

apt is the resolver and fetcher. apt install nginx reads the package lists under /var/lib/apt/lists/, computes every dependency, downloads each .deb into /var/cache/apt/archives/, and hands them to dpkg in the correct order. The status database that dpkg maintains is what apt consults to know what is already present.

# apt drives the whole flow; dpkg does the on-disk work
sudo apt install nginx       # resolve + download + call dpkg
dpkg -i ./mypkg_1.2_amd64.deb  # local file only, no deps fetched

Everyday apt

apt update refreshes the local copy of repository metadata; it installs nothing. apt upgrade installs newer versions of packages you already have but will never remove a package to do it, which is why it can leave updates "kept back". apt full-upgrade (formerly dist-upgrade) is allowed to remove packages to satisfy a dependency change, so it completes those held-back upgrades.

apt remove deletes a package's program files but leaves its config under /etc; apt purge removes the config too. apt autoremove drops dependencies that were pulled in automatically and are no longer needed by anything.

The apt command is the human-facing front end. For scripts and CI, use apt-get and apt-cache: their output format is a stable contract, while the apt command prints a progress bar and color and explicitly warns that its output is not meant to be parsed.

Taskapt (Debian/Ubuntu)dnf (Red Hat)
Refresh metadataapt updateautomatic per command
Upgrade allapt full-upgradednf upgrade
Remove + configapt purgednf remove
Drop orphansapt autoremovednf autoremove

Inspecting with dpkg

dpkg is the query and repair layer. dpkg -l lists installed packages with their state flags; dpkg -L nginx lists every file a package shipped; dpkg -S /usr/sbin/nginx tells you which package owns a given file on disk. These three answer most "where did this come from" questions during an incident.

When an install is interrupted — a lost connection, a killed process, a full disk — packages are left in a half-configured state and apt refuses to proceed. dpkg --configure -a finishes the configuration step for every such package. If the break is a missing dependency, apt install -f ("fix broken") downloads and installs what is needed to make the dependency graph consistent again.

# map a file to its package, then recover a broken state
dpkg -S /etc/nginx/nginx.conf   # nginx-common: /etc/nginx/nginx.conf
sudo dpkg --configure -a       # finish interrupted configures
sudo apt install -f            # pull missing dependencies

Sources and Keys

Repositories are declared in /etc/apt/sources.list and in drop-in files under /etc/apt/sources.list.d/. Each entry names a URL, a release suite (such as noble or bookworm), and the components to enable (main, restricted, universe). One file per third-party repository keeps the configuration auditable and easy to remove.

Repository authentication has changed. The old apt-key add added a key to a single global keyring trusted for every repository — a security flaw, since any one repository's key could then sign any package. It is deprecated — still shipped in Debian 12 and Ubuntu 24.04 but slated for removal. Keys now live as files in /etc/apt/keyrings/, and each source entry points at its own key with the signed-by= option, so a key is trusted only for the repository that ships it.

# /etc/apt/sources.list.d/docker.sources — one repo, its own key
Types: deb
URIs: https://download.docker.com/linux/ubuntu
Suites: noble
Components: stable
Signed-By: /etc/apt/keyrings/docker.gpg

Holds, Pins, and Unattended Upgrades

To freeze a package at its current version, use sudo apt-mark hold nginx; apt skips it during upgrades until you run apt-mark unhold. For finer control across repositories, apt pinning lives in files under /etc/apt/preferences.d/, assigning a numeric priority to a package or release — the default is 500, and a priority of 1000 or above even permits a downgrade. To install one specific version directly, name it: apt install nginx=1.24.0-2ubuntu7.

For servers, the unattended-upgrades package applies security updates automatically on a timer, which closes the window between a CVE disclosure and a human getting around to patching. Configure it to install from the security suite only, and set Unattended-Upgrade::Automatic-Reboot deliberately — automatic reboots clear kernel updates but will drop connections, so schedule them in a maintenance window.

remove vs purge vs autoremove

apt remove — deletes the package's program files but leaves its configuration under /etc. A later reinstall picks the old config back up, which is exactly why "I reinstalled and it didn't reset" surprises people.

apt purge — removes the package and its config files together. Reach for it when you want a clean slate and intend to reconfigure from scratch.

apt autoremove — targets neither directly. It drops dependencies that were pulled in automatically for some package and are now needed by nothing. Run it after removals to reclaim space, but read the list first, since a wrongly-marked package can take a tool you still use.

Common Mistakes
  • Running apt upgrade and ignoring "the following packages have been kept back" — they are held because the upgrade needs to add or remove a package, which only apt full-upgrade will do, so the security fix you wanted never lands.
  • Adding a repository key with apt-key add into the deprecated global keyring instead of a per-repo /etc/apt/keyrings/ file referenced by signed-by= — that key is then trusted to sign packages from every repository, not just its own.
  • Running sudo dpkg -i app.deb for a downloaded app and walking away when it errors on a missing dependency, leaving it half-installed instead of finishing with apt install -f.
  • Using apt remove when you meant apt purge — the leftover config in /etc resurfaces on reinstall and produces behavior that does not match a fresh setup.
  • Skipping apt update before apt install, so the candidate version is stale and the download 404s when the repository has already rotated that file off the mirror.
  • Interrupting an install or upgrade and not running dpkg --configure -a, so every later apt run refuses to proceed against a half-configured database.
  • Parsing the apt command's output in a script — it is documented as unstable, and a future release can change the format and silently break the pipeline; use apt-get/apt-cache there.
Best Practices
  • Run apt update immediately before any apt install so the candidate list and download URLs are current.
  • Use apt purge when you want a package gone including its config, and apt autoremove afterward to drop orphaned dependencies.
  • Add third-party repositories with a per-repo key in /etc/apt/keyrings/ referenced by signed-by= — never apt-key add.
  • Find which package owns a file with dpkg -S, and list a package's files with dpkg -L, when tracing where something on disk came from.
  • After a local dpkg -i, run apt install -f to pull and configure the dependencies dpkg could not fetch.
  • Enable unattended-upgrades scoped to the security suite on servers, and set automatic reboots only inside a maintenance window.
  • Freeze critical packages with apt-mark hold or an /etc/apt/preferences.d/ pin, and leave a comment recording why.
Comparable toolsRHEL — dnf/rpm, the same two-layer split with different command namesArch — pacman, a single tool covering both layersAlpine — apk, the lightweight package manager common in containers

Knowledge Check

An apt upgrade reports packages "kept back". What completes those upgrades?

  • apt full-upgrade, which is permitted to add or remove packages to satisfy the dependency change
  • Running apt update again to refresh the repository metadata before retrying the upgrade
  • dpkg --configure -a to finish the configuration step the upgrade left half-done and held back on the held packages
  • apt autoremove first to clear the orphaned packages that are pinning the held versions in place and blocking the upgrade

You run dpkg -i on a downloaded .deb and it fails on a missing dependency. What resolves it?

  • apt install -f, which resolves and downloads the missing dependencies that dpkg cannot fetch
  • Re-running dpkg -i with --force-all to override the dependency check and push the install through
  • apt purge the half-installed package and reinstall it from a configured repository
  • apt update to refresh the metadata, since the candidate version must be stale

Why register a third-party repo's key with signed-by= in /etc/apt/keyrings/ rather than apt-key add?

  • A signed-by= key is trusted only for its own repository; the legacy global keyring trusted every key to sign any package
  • apt-key add cannot import the modern dearmored binary GPG key formats that vendors now ship in their installation docs, so the import always fails outright
  • A key in the keyrings directory lets apt update verify each repository's index noticeably faster because it skips the slower global trust lookup
  • A key placed in /etc/apt/keyrings/ is required before dpkg will install any local .deb file, since it verifies the embedded signature first

Why prefer apt-get over the apt command in a deployment script?

  • apt-get keeps a stable output contract across releases; the apt command's output is explicitly documented as unstable
  • apt-get resolves the full dependency tree properly while the apt command silently ignores some recommended and suggested packages during the same install
  • The apt command cannot run at all without an attached interactive terminal, so it aborts immediately when invoked from a non-interactive deployment script
  • apt-get is faster in scripts because it skips downloading the repository index metadata and reuses whatever stale lists are already cached on disk

You got correct