Linux · lesson 6 of 7
Beginner
2Intermediate
5- 2 Permission Bits, Ownership, and Why passwd Needs setuid
- 3 A Zombie Process That Never Gets Reaped — and the PID 1 That Fixes It
- 4 systemd Service Supervision: Restart Limits and Why They Trip
- 5 Linux Networking: Routes, Interfaces, and Bind Addresses
- 7 Inodes: The Filesystem Is Full, but df -h Says 1% Used
Also part of Linux
7- + Bash Text Processing from Bash
- + Bash Process Management from Bash
- + Bash System Information from Bash
- + Cron and Scheduling in Bash from Bash
- + Bash File Operations from Bash
- + Reading Linux SSH Auth Logs: Failed Logins You Generate Yourself from Defensive Security
- + Host Firewalls with nftables: DROP Times Out, REJECT Refuses from Networking
apt vs dpkg: A Real Broken Dependency, and How apt Actually Fixes It
dpkg -i installs a .deb but refuses to configure it when dependencies are missing — leaving a package that half-works. The real fix isn't reinstalling it; it's letting apt resolve what dpkg couldn't.
By SkillByExample · Published
dpkg -i on a single file looks like the obvious way to install a .deb — and it works,
right up until that package needs something dpkg has no way to fetch on its own.
Installing a .deb directly with dpkg
docker run -d --name pkg-lesson ubuntu:22.04 sleep infinity
docker exec pkg-lesson bash -c "apt-get update -qq && cd /tmp && apt-get download git"
docker exec pkg-lesson bash -c "cd /tmp && dpkg -i git_1%3a2.34.1-1ubuntu1.17_amd64.deb"
Selecting previously unselected package git.
(Reading database ... 4393 files and directories currently installed.)
Preparing to unpack git_1%3a2.34.1-1ubuntu1.17_amd64.deb ...
Unpacking git (1:2.34.1-1ubuntu1.17) ...
dpkg: dependency problems prevent configuration of git:
git depends on libcurl3-gnutls (>= 7.56.1); however:
Package libcurl3-gnutls is not installed.
git depends on libexpat1 (>= 2.0.1); however:
Package libexpat1 is not installed.
git depends on perl; however:
Package perl is not installed.
git depends on liberror-perl; however:
Package liberror-perl is not installed.
git depends on git-man (>> 1:2.34.1); however:
Package git-man is not installed.
git depends on git-man (<< 1:2.34.1-.); however:
Package git-man is not installed.
dpkg: error processing package git (--install):
dependency problems - leaving unconfigured
Errors were encountered while processing:
git
apt-get download fetched only git’s own file, with none of its dependencies — exactly what
dpkg -i needs to fail this way, since dpkg unpacks whatever file you hand it without ever
checking a repository for what else that file needs.
The bug: a package that’s unpacked but not configured
docker exec pkg-lesson dpkg -s git
Package: git
Status: install ok unpacked
unpacked, not installed — the files are on disk, but dpkg never finished setting the
package up. Commands that don’t touch the missing libraries still work:
docker exec pkg-lesson git --version
docker exec pkg-lesson bash -c "cd /tmp && mkdir testrepo && cd testrepo && git init"
git version 2.34.1
Initialized empty Git repository in /tmp/testrepo/.git/
But git clone over HTTPS runs through a separate helper binary linked against the missing
library:
docker exec pkg-lesson bash -c "git clone https://github.com/git/git.git /tmp/clonetest"
Cloning into '/tmp/clonetest'...
/usr/lib/git-core/git-remote-https: error while loading shared libraries: libcurl-gnutls.so.4: cannot open shared object file: No such file or directory
Not “command not found,” not “git: not installed” — a shared-library load failure from a helper binary the user never invokes directly. This is what a half-configured package actually looks like in practice: some subcommands work, others fail in a way that doesn’t obviously point back to a missing dependency.
The fix: let apt finish what dpkg couldn’t
docker exec pkg-lesson apt-get install -f -y
Setting up libgdbm6:amd64 (1.23-1) ...
Setting up libldap-2.5-0:amd64 (2.5.20+dfsg-0ubuntu0.22.04.1) ...
Setting up ca-certificates (20260601~22.04.1) ...
Setting up libgdbm-compat4:amd64 (1.23-1) ...
Setting up libperl5.34:amd64 (5.34.0-3ubuntu1.9) ...
Setting up libcurl3-gnutls:amd64 (7.81.0-1ubuntu1.27) ...
Setting up perl (5.34.0-3ubuntu1.9) ...
Setting up liberror-perl (0.17029-1) ...
Setting up git (1:2.34.1-1ubuntu1.17) ...
-f (“fix broken”) tells apt to look at whatever dpkg left half-done, resolve the actual
dependency chain, download every missing package, and finish configuring git last. Nothing
re-downloads git itself — only the six missing dependencies dpkg listed earlier.
docker exec pkg-lesson dpkg -s git
docker exec pkg-lesson bash -c "rm -rf /tmp/clonetest && git clone --depth 1 https://github.com/git/git.git /tmp/clonetest && ls /tmp/clonetest | head -3"
Status: install ok installed
Cloning into '/tmp/clonetest'...
CODE_OF_CONDUCT.md
COPYING
Cargo.toml
installed, not unpacked — and the exact clone that failed before now succeeds, using the
same package that was already on disk the whole time.
Practice
1. List an installed package's version the way dpkg -l shows it.
docker exec pkg-lesson dpkg -l git
||/ Name Version Architecture Description
+++-==============-====================-============-===================================================
ii git 1:2.34.1-1ubuntu1.17 amd64 fast, scalable, distributed revision control system
ii in the first column means “installed, correctly configured” — during the broken window
earlier this would have shown a different pair of letters (iU, for unpacked-but-not-yet-
configured), which is a faster way to spot a broken package across an entire system than
checking each one individually with dpkg -s.
2. Check whether a newer version of an installed package is available before upgrading.
docker exec pkg-lesson apt-cache policy git
git:
Installed: 1:2.34.1-1ubuntu1.17
Candidate: 1:2.34.1-1ubuntu1.17
Version table:
*** 1:2.34.1-1ubuntu1.17 500
500 http://archive.ubuntu.com/ubuntu jammy-updates/main amd64 Packages
500 http://security.ubuntu.com/ubuntu jammy-security/main amd64 Packages
100 /var/lib/dpkg/status
1:2.34.1-1ubuntu1 500
500 http://archive.ubuntu.com/ubuntu jammy/main amd64 Packages
Installed matches Candidate here, so apt upgrade would leave git alone. The second,
older version listed underneath (jammy/main, without -updates) shows exactly which
repository a security or point-release update actually came from.
3. Find every binary a package actually installed, without guessing paths.
docker exec pkg-lesson bash -c "dpkg -L git | grep bin"
/usr/bin
/usr/bin/git
/usr/bin/git-shell
/usr/bin/git-receive-pack
/usr/bin/git-upload-archive
/usr/bin/git-upload-pack
dpkg -L lists every file a package owns — this is the reverse of dpkg -S <path>, which
answers “what package owns this file.” Between the two, there’s rarely a need to guess where
a package put something or what a package would remove.
Next: filesystems and disk usage — inodes, hard versus soft links, and what actually happens when a filesystem runs out of inodes before it runs out of space.