apt and dpkg
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.
| Task | apt (Debian/Ubuntu) | dnf (Red Hat) |
|---|---|---|
| Refresh metadata | apt update | automatic per command |
| Upgrade all | apt full-upgrade | dnf upgrade |
| Remove + config | apt purge | dnf remove |
| Drop orphans | apt autoremove | dnf 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.
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.
- Running
apt upgradeand ignoring "the following packages have been kept back" — they are held because the upgrade needs to add or remove a package, which onlyapt full-upgradewill do, so the security fix you wanted never lands. - Adding a repository key with
apt-key addinto the deprecated global keyring instead of a per-repo/etc/apt/keyrings/file referenced bysigned-by=— that key is then trusted to sign packages from every repository, not just its own. - Running
sudo dpkg -i app.debfor a downloaded app and walking away when it errors on a missing dependency, leaving it half-installed instead of finishing withapt install -f. - Using
apt removewhen you meantapt purge— the leftover config in/etcresurfaces on reinstall and produces behavior that does not match a fresh setup. - Skipping
apt updatebeforeapt 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 lateraptrun refuses to proceed against a half-configured database. - Parsing the
aptcommand's output in a script — it is documented as unstable, and a future release can change the format and silently break the pipeline; useapt-get/apt-cachethere.
- Run
apt updateimmediately before anyapt installso the candidate list and download URLs are current. - Use
apt purgewhen you want a package gone including its config, andapt autoremoveafterward to drop orphaned dependencies. - Add third-party repositories with a per-repo key in
/etc/apt/keyrings/referenced bysigned-by=— neverapt-key add. - Find which package owns a file with
dpkg -S, and list a package's files withdpkg -L, when tracing where something on disk came from. - After a local
dpkg -i, runapt install -fto pull and configure the dependenciesdpkgcould not fetch. - Enable
unattended-upgradesscoped to the security suite on servers, and set automatic reboots only inside a maintenance window. - Freeze critical packages with
apt-mark holdor an/etc/apt/preferences.d/pin, and leave a comment recording why.
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 updateagain to refresh the repository metadata before retrying the upgrade dpkg --configure -ato finish the configuration step the upgrade left half-done and held back on the held packagesapt autoremovefirst 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 thatdpkgcannot fetch- Re-running
dpkg -iwith--force-allto override the dependency check and push the install through apt purgethe half-installed package and reinstall it from a configured repositoryapt updateto 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 addcannot 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 updateverify each repository's index noticeably faster because it skips the slower global trust lookup - A key placed in
/etc/apt/keyrings/is required beforedpkgwill install any local.debfile, since it verifies the embedded signature first
Why prefer apt-get over the apt command in a deployment script?
apt-getkeeps a stable output contract across releases; theaptcommand's output is explicitly documented as unstableapt-getresolves the full dependency tree properly while theaptcommand silently ignores some recommended and suggested packages during the same install- The
aptcommand cannot run at all without an attached interactive terminal, so it aborts immediately when invoked from a non-interactive deployment script apt-getis faster in scripts because it skips downloading the repository index metadata and reuses whatever stale lists are already cached on disk
You got correct