Skip to content

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

dpkg -l nginx

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

dpkg -s nginx

-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

dpkg -L nginx

Sample output (trimmed):

/.
/etc
/etc/nginx
/etc/nginx/nginx.conf
/usr/sbin/nginx
/lib/systemd/system/nginx.service

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

dpkg -S /usr/sbin/nginx
nginx-core: /usr/sbin/nginx

-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:

dpkg -c package.deb

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:

sudo dpkg -i package.deb

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

sudo apt --fix-broken install

(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:

grep -A2 "^Package: nginx$" /var/lib/dpkg/status
Package: nginx
Status: install ok installed
Priority: optional

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 | tail -n +6 | grep -v '^ii'

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.

dpkg -s package

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.

sudo dpkg --configure package

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.

sudo apt --fix-broken install

Verify:

dpkg -l package

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"

sudo dpkg -i --force-depends package.deb

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:

sudo dpkg --purge package

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

  1. Count how many packages are fully installed on your system with dpkg -l | grep ^ii | wc -l, then check with dpkg -l | grep ^rc whether any removed-but-not-purged packages exist.
  2. Find which package a file you already know (for example /usr/bin/bash) belongs to using dpkg -S, then use dpkg -L to see what other files that same package installed.
  3. Download a small, harmless .deb file (for example with apt download <package> from the repository) and inspect its contents with dpkg -c without 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