dpkg and Dependencies: Working With the Low-Level Layer
As apt and Repositories showed, running apt install triggers the high-level layer to find a package and its dependencies in a repository, then hand the actual installation off to the low-level tool. On the Debian/Ubuntu family, that tool is dpkg (Debian Package). apt relies on dpkg almost universally underneath, which is why, when something unusual happens with a package — an install stuck halfway, a dependency conflict, or a need to determine exactly which file belongs to which package — the answer is often found not at the apt level, but at the dpkg level directly.
This article covers working directly with dpkg: how it tracks installed package state, why a dependency can genuinely "break" at this level, and how to diagnose and fix that situation.
What dpkg Is and How It Relates to apt
Recall the two-layer model from Package Management Basics: dpkg is the low-level tool. It takes one specific .deb file, writes it onto the system, removes it when asked, and maintains the list of installed packages. But dpkg has one important limitation: it only checks dependencies, it does not go looking for them. If a .deb file declares "I need libssl3" and that package is not on the system, dpkg simply reports an error and stops — it never reaches out to the internet to find it. apt exists precisely to fill that gap.
flowchart LR
A["apt install package"] --> B["Finds package and dependencies\nin the repository"]
B --> C["Downloads each one"]
C --> D["Installs them one by one\nvia dpkg -i"]
Because of this, ordinary day-to-day work usually only needs apt. Working with dpkg directly is typically needed in two situations: installing a .deb file that was downloaded manually and is not in any repository, or needing precise, detailed information about an installed package — such as exactly which files it placed on the system.
Getting Information About Installed Packages
Listing and Status
Sample output:
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
+++-==============-====================-============-===============
ii nginx 1.24.0-2ubuntu7 amd64 small, powerful, scalable web/proxy server
The first two letters are the status code. The ones you will encounter most often:
| Code | Meaning |
|---|---|
ii |
Fully installed (install requested, install completed) |
rc |
Removed, but configuration files remain (remove was run, not purge) |
iU |
Install requested, but not yet configured — usually a sign of an interrupted install |
iF |
Configuration half-completed — a broken state requiring a fix |
The rc state is exactly the dpkg-level counterpart of the remove/purge distinction covered in apt and Repositories: a package moves to this state after apt remove.
Full Detail
-s (status) shows a package's full metadata — version, dependencies, install date, description. This is, in effect, the low-level equivalent of apt show, but working only against a package that is already installed locally.
Which Files a Package Installed
Sample output (trimmed):
This is one of the most useful troubleshooting tools available: it answers "which package does this configuration file come from" or "which files will disappear if this package is removed" precisely.
Finding Which Package a File Belongs To — the Reverse Direction
-L finds files from a package name; -S finds the package name from a file — the reverse operation. This is especially useful when you encounter an unfamiliar file on a system and need to identify what installed it.
Inspecting a .deb File Before Installing It
To preview what a not-yet-installed .deb file actually contains:
This is a safe, read-only way to see exactly which files would land where, before you commit to installing anything.
Installing a .deb File Directly and the Dependency Problem
Sometimes the software you need is not in any repository and is distributed only as a standalone .deb file (a browser, a specialized application). In that case:
If this package depends on other packages that are not yet installed, the output looks like:
Selecting previously unselected package package.
(Reading database ... 123456 files and directories currently installed.)
Preparing to unpack package.deb ...
Unpacking package (1.0-1) ...
dpkg: dependency problems prevent configuration of package:
package depends on libexample2 (>= 2.0); however:
Package libexample2 is not installed.
dpkg: error processing package package (--configure):
dependency problems - leaving unconfigured
Errors were encountered while processing:
package
This is dpkg behaving exactly as designed, not a bug: it checked the dependency as stated, could not find it, and stopped. The package is now written to disk but left in an unconfigured state (dpkg -l shows such a package as iU).
The Fix: Letting apt Finish the Dependency Resolution
(the older equivalent is apt-get install -f). This command reads dpkg's status database, finds packages left in an unconfigured state, downloads their missing dependencies from the repository, and completes the installation. Precisely because dpkg cannot search for a dependency itself, it is standard practice to expect this command to be needed immediately after installing a standalone .deb file.
Tip
Practical rule: after dpkg -i on a manually downloaded .deb file, run apt --fix-broken install right away — even if no error appeared. This is safe regardless: if dependencies were already satisfied, the command simply reports that nothing needs to be done.
The dpkg Status Database: /var/lib/dpkg
dpkg stores information about every installed package as plain text in /var/lib/dpkg/status. This file is the single source of truth for "which package is installed" on the entire system:
Danger
Do not manually edit or delete this file. It is dpkg's internal state database — an incorrect change can destabilize the entire package system. Use it only for reading, as a diagnostic source; any actual change should always go through dpkg/apt commands.
A Practical Scenario: Diagnosing and Fixing a Half-Installed Package
Problem. A colleague manually downloaded a .deb file and installed it with dpkg -i, but it produced an error, and the server was left in an unknown state for you to sort out.
Step 1. Find packages in a broken state.
dpkg -l's output always begins with 5 header lines, so tail -n +6 skips them. Among the remaining lines, grep -v '^ii' isolates anything whose status code is not ii (fully installed) — such as iU or iF. An empty result means no broken package exists.
Step 2. Check the specific problem package's exact state.
The output showing something like Status: install ok unpacked (written, but not configured) — distinct from Status: install ok installed — confirms the intermediate, broken state.
Step 3. Identify exactly which dependency is missing.
This attempts to finish the incomplete configuration, and if the dependency is still missing, returns the same precise error message (naming the required package) as before.
Step 4. Complete the dependency resolution through apt.
Verify:
The status code should now read ii.
Conclusion. The root cause was installing a .deb file manually, outside a repository, which collided with dpkg's inability to search for dependencies on its own. The fix is technically simple (apt --fix-broken install), but finding it required correctly reading the low-level state first, using dpkg -l and dpkg -s.
Common Mistakes
Ignoring a Dependency Error After dpkg -i
Leaving a package in this state "to deal with later" means it stays half-installed and can interfere with future apt upgrade/install operations. Run apt --fix-broken install the moment the error appears.
Using --force-* Flags as a "Quick Fix"
Danger
--force-depends bypasses dependency checking entirely — the package is marked "installed" even though a library it needs is genuinely missing. In practice, the software may fail to start or behave incorrectly. This flag is reserved for very specific, deliberately justified cases; on a production server it is almost always the wrong fix — resolve the actual missing dependency instead.
Treating a Package in rc State as Still Installed
A package showing rc in dpkg -l does not actually function — it has been removed, only configuration remnants remain. To clean it up completely:
Attempting to Manually Edit /var/lib/dpkg/status
This file is dpkg's internal database. A manual change disconnects the recorded package state from the system's actual state and can destabilize any future apt/dpkg operation.
Exercises
- Count how many packages are fully installed on your system with
dpkg -l | grep ^ii | wc -l, then check withdpkg -l | grep ^rcwhether any removed-but-not-purged packages exist. - Find which package a file you already know (for example
/usr/bin/bash) belongs to usingdpkg -S, then usedpkg -Lto see what other files that same package installed. - Download a small, harmless
.debfile (for example withapt download <package>from the repository) and inspect its contents withdpkg -cwithout installing it — determine exactly which files it would place where. This action changes nothing.
Verification criterion: for each exercise, you should be able to correctly read a dpkg status code (ii, rc, and so on), explain in words exactly what stage that package is in, and trace a file-to-package relationship using -L/-S.
Summary
dpkg is the Debian/Ubuntu family's low-level package tool: it installs and removes a single .deb file and maintains installed-package state under /var/lib/dpkg/, but only checks dependencies rather than searching for them. Most of what apt does is a convenience layer built directly on top of this. A dependency error after installing a .deb file directly is expected behavior, not a malfunction, and apt --fix-broken install is the standard way to resolve it.
These two articles — apt and dpkg — cover the core of the Debian/Ubuntu ecosystem. The next article, Snap Packages, covers an entirely different approach — a distribution-independent, self-updating, containerized package format — and when it is a reasonable alternative to apt, and when it is not.
References
- Linux man-pages:
dpkg(1): https://man7.org/linux/man-pages/man1/dpkg.1.html - Linux man-pages:
dpkg-query(1): https://man7.org/linux/man-pages/man1/dpkg-query.1.html - Debian Wiki: dpkg: https://wiki.debian.org/dpkg
- Debian Policy Manual: package maintainer scripts and installation procedure: https://www.debian.org/doc/debian-policy/ch-maintainerscripts.html
- Ubuntu Server documentation: package management: https://ubuntu.com/server/docs/package-management