LPIC-1 Package Management: dpkg/apt, rpm/dnf, and Shared Libraries
- Explain why dpkg installs a single .deb with no dependency resolution while apt resolves dependencies from repositories
- Inspect a .deb's version, dependencies, and file list with dpkg-deb before installing it
- Repair a half-installed package whose dependencies are missing with apt -f install
- Hold a package with apt-mark, and change and verify apt's settings with apt-config dump
- Find which package owns a file, and conversely list every file a package installed, on both Debian and RPM systems
- Read ldd output to confirm a binary's shared libraries resolve, and use ldconfig to fix a 'not found' library after adding a new path
Why two tools to install software?
Understand the division of labour between dpkg (one file) and apt (the whole web of dependencies).
On a Debian-family system there are two programs that install software, and the exam loves to test whether you know which does what.
- `dpkg` is the low-level tool. Hand it a single
.debfile and it unpacks and installs that one package — nothing more. It does not reach out to find anything the package depends on. - `apt` is the high-level tool. It reads your configured repositories, works out the full chain of dependencies a package needs, downloads them all, and installs them in the right order.
Think of it like furniture.
dpkgis a person who will assemble exactly the one box you place in front of them.aptis the project manager who reads the instructions, realises you also need screws, a shelf bracket, and a wall anchor, drives to the store for all of them, and then hands the whole job to the assembler.Check yourself
Which tool resolves and downloads a package's dependencies from the configured repositories: dpkg or apt?
- `dpkg` is the low-level tool. Hand it a single
dpkg-deb: look inside a .deb before you install it
Read a package's version, dependencies, and file list straight from the .deb, without installing anything.
A
.debis just an archive: a control section (name, version, dependencies, description) plus the files it will drop onto your system.dpkg-debis the tool that works on that archive directly. It never touches the package database, so it's how you vet a vendor's package beforedpkg -igets anywhere near it: which version is it, what does it insist on, and where will it put its files?To have a real package to inspect, build a tiny one.
dpkg-deb --buildturns a directory (aDEBIAN/controlfile plus the tree to install) into a.deb:mkdir -p hello-demo/DEBIAN hello-demo/usr/bin cp /bin/true hello-demo/usr/bin/hello-demo printf 'Package: hello-demo\nVersion: 1.2-1\nArchitecture: all\nMaintainer: Lab <lab@example.com>\nDepends: libc6 (>= 2.34)\nRecommends: hello-demo-doc\nDescription: tiny demo package\n' > hello-demo/DEBIAN/control dpkg-deb --build --root-owner-group hello-demodpkg-deb: building package 'hello-demo' in 'hello-demo.deb'.
- --build DIR FILE
- Pack DIR (its DEBIAN/control plus the file tree) into a .deb, named DIR.deb unless you give FILE. Short form: -b.
- --root-owner-group
- Record every file as owned by root:root, the way real packages ship, whoever builds it.
The control file declares one hard dependency ( Depends: libc6 (>= 2.34)) and one optional one (Recommends: hello-demo-doc), and the tree ships a single program at/usr/bin/hello-demo(a copy of/bin/truestanding in for a real tool). Nothing is installed: you now holdhello-demo.debin the current directory, exactly like a file a vendor would hand you.Now inspect it the way you would any package you didn't build.
dpkg-deb -f(--field) prints control fields; name one field and you get just its value, ready to redirect into a file:dpkg-deb -f hello-demo.deb Version1.2-1
- -f DEB FIELD...
- Print control fields. One field prints its bare value; several print
Field: valuelines. Long form: --field.
The bare version string, no label. That is the form you want when a script or a change ticket needs the value: dpkg-deb -f pkg.deb Version > version.txt.dpkg-deb -f hello-demo.deb Depends RecommendsDepends: libc6 (>= 2.34) Recommends: hello-demo-doc
Ask for two fields and each comes back labelled. `Depends` is the hard requirement: the package will not configure without libc62.34 or newer. `Recommends` is a strong suggestion that apt installs by default but that the package can live without. The package name in a dependency is the first word;(>= 2.34)is a version constraint, not part of the name.dpkg-deb -c hello-demo.debdrwxr-xr-x root/root 0 2026-10-02 17:14 ./ drwxr-xr-x root/root 0 2026-10-02 17:14 ./usr/ drwxr-xr-x root/root 0 2026-10-02 17:14 ./usr/bin/ -rwxr-xr-x root/root 26936 2026-10-02 17:14 ./usr/bin/hello-demo
- -c
- List the files the package would install, tar-style. Long form: --contents.
- -I
- Show the package's info: archive sizes plus the full control file. Long form: --info.
A tar tv-style listing: permissions, owner, size, date, and the path in the last column. Paths are relative to/(./usr/bin/hello-demoinstalls as/usr/bin/hello-demo). The dates are when the package was built (and the size is your/bin/true), so yours differ. For the whole control file in one go, rundpkg-deb -Ion the same file. Clean up withrm -r hello-demo hello-demo.deb.Check yourself
Which dpkg-deb option lists the files inside a .deb without installing it?
dpkg -i: install one file, dependencies and all your problem
See exactly how dpkg fails when a dependency is missing — and why that's by design.
Let's install a
.debdirectly withdpkg -i. This is what you do when you've downloaded a package by hand (say a vendor's.deb) rather than pulling it from a repository.Example — not runnable on your lab box
sudo dpkg -i ./tree_2.1.1-1_amd64.debSelecting previously unselected package tree. (Reading database ... 41203 files and directories currently installed.) Preparing to unpack tree_2.1.1-1_amd64.deb ... Unpacking tree (2.1.1-1) ... dpkg: dependency problems prevent configuration of tree: tree depends on libc6 (>= 2.34); however: Package libc6 is not installed. dpkg: error processing package tree (--install): dependency problems - leaving unconfigured Errors were encountered while processing: tree
- -i
- Install (or upgrade) the named .deb file. Long form: --install.
Read this carefully: dpkgunpacked the package, then stopped at configuration becauselibc6isn't installed. It tells you the missing dependency but it will not go and get it. The package is now in a half-installed state — unpacked but unconfigured. This isn't a bug;dpkgonly knows about the one file you gave it.Check yourself
After
dpkg -ireports 'dependency problems - leaving unconfigured', what state is the package in?apt -f install: let apt clean up dpkg's mess
Repair broken dependencies left behind by a manual dpkg install.
You're now holding a half-installed package. Because
aptcan see the repositories, you can ask it to fix the broken state — it will find the missing dependency (libc6here), download it, install it, and then finish configuring the packagedpkgleft hanging.sudo apt -f installReading package lists... Done Building dependency tree... Done Correcting dependencies... Done The following additional packages will be installed: libc6 The following NEW packages will be installed: libc6 0 upgraded, 1 newly installed, 0 to remove and 0 not upgraded. 1 not fully installed or removed. Setting up libc6 (2.35-0ubuntu3) ... Setting up tree (2.1.1-1) ...
- -f
- Fix broken dependencies. Long form: --fix-broken. With no package name it repairs whatever is currently broken.
- install
- The apt subcommand to install/configure; combined with -f it completes half-installed packages.
apt -f installnoticed1 not fully installed, pulled inlibc6, and then ran `Setting up tree` — finishing the exact configuration stepdpkghad abandoned. The package is now fully installed. This is the standard recovery move after a manual.debinstall goes wrong.Check yourself
Which single command tells apt to find and install whatever dependencies are missing to repair a broken package?
Steer apt: holds with apt-mark, settings with apt-config
Keep a validated package from being upgraded, and change and verify apt's own behaviour.
Installing is half the job; the other half is controlling what apt does later. Two situations come up constantly.
A package must not move. You validated your app against one version of a database driver, and the nightly
apt upgrade(or unattended-upgrades) must leave it alone. You put it on hold. A hold is a flag in dpkg's local selection state under/var/lib/dpkg/, so it needs no network and installs nothing.apt-marksets it, lists it, and clears it.sudo apt-mark hold nanonano set on hold.
- hold PKG...
- Freeze one or more packages at their installed version; upgrades skip them.
- unhold PKG...
- Clear the hold so the package upgrades normally again.
From now on apt upgradereportsnanounder kept back instead of upgrading it. You can hold several packages in one command:apt-mark hold pkgA pkgB.apt-mark showholdnano
Every package currently on hold, one per line. This reads the same durable state apt consults before an upgrade, so it is the check to run when you need proof a hold took, rather than waiting for the next upgrade to find out. sudo apt-mark unhold nanoCanceled hold on nano.
Releases the hold. Reverse holds deliberately when the validation window ends, or the package quietly misses security updates forever. apt itself must behave differently. apt's settings live in many small files under
/etc/apt/apt.conf.d/, read in name order, with a later file overriding an earlier one. So the file you're looking at may not be the value apt actually uses.apt-config dumpprints the merged, effective configuration; name a key to see just that key. The classic change is to stop apt pulling in everyRecommendsandSuggestson install:apt-config dump APT::Install-Recommends APT::Install-SuggestsAPT::Install-Recommends "1"; APT::Install-Suggests "0";
- dump [KEY...]
- Print the effective configuration after every apt.conf.d file is merged; with keys, only those keys.
The defaults: apt installs a package's Recommends ( "1") but not its Suggests ("0"). On a lean server you usually want both off.printf 'APT::Install-Recommends "false";\nAPT::Install-Suggests "false";\n' | sudo tee /etc/apt/apt.conf.d/99-lean.conf >/dev/null apt-config dump APT::Install-Recommends APT::Install-SuggestsAPT::Install-Recommends "false"; APT::Install-Suggests "false";
Write the setting in apt's own syntax ( Key "value";) into a high-numbered file so it is read last and wins, then confirm withapt-config dump. Both keys now sayfalse. Verifying the effective value is what catches the file that was shadowed by another one, or the key that was misspelled and silently ignored. Delete/etc/apt/apt.conf.d/99-lean.confto go back to the defaults.Check yourself
Which command lists every package that is currently on hold?
Two directions of querying: owns a file vs. lists files
Cleanly separate 'which package owns this file?' from 'what files did this package install?' on both Debian and RPM systems.
Package querying always goes one of two opposite directions, and confusing them costs easy exam marks. Keep this picture in your head:
- File → Package ("who owns this file?"): you have a path and want the package it came from.
- Package → Files ("what did this package put on disk?"): you have a package name and want its file list.
The arrows point opposite ways, and each distro family has its own pair of commands.
Direction 1 — File → Package (owns a file). On Debian use
dpkg -S; on RPM userpm -qf. Give it a path, get back the package.dpkg -S /usr/bin/treetree: /usr/bin/tree
- -S
- Search: given a file path, print the package that owns it. Long form: --search.
The output reads package: path. So/usr/bin/treeis owned by the `tree` package. Mnemonic: S for Search (you're searching for the owner of a file).Example — not runnable on your lab box
rpm -qf /usr/bin/treetree-2.1.1-2.fc40.x86_64
- -q
- Query mode — every rpm read operation starts with -q.
- -f
- ...by File: given a path, name the package that owns it.
The RPM equivalent of dpkg -S. It returns the full package NVR (name-version-release-arch). Mnemonic: query file-owner.Direction 2 — Package → Files (lists files). On Debian use
dpkg -L; on RPM userpm -ql. Give it a package name, get back every file it installed.dpkg -L tree/. /usr /usr/bin /usr/bin/tree /usr/share /usr/share/man /usr/share/man/man1 /usr/share/man/man1/tree.1.gz
- -L
- List the files installed by the named package. Long form: --listfiles.
Now the input is a package name and the output is its file list — the binary, the man page, and the directories it owns. Mnemonic: L for List files. Example — not runnable on your lab box
rpm -ql tree/usr/bin/tree /usr/share/doc/tree /usr/share/doc/tree/README /usr/share/man/man1/tree.1.gz
- -q
- Query mode.
- -l
- ...List the files contained in the named package (lowercase L).
The RPM equivalent of dpkg -L. Mnemonic: query, list files.Check yourself
You have the path /etc/ssh/sshd_config and want to know which package installed it. Name a command that answers this (Debian or RPM).
ldd: does this binary's shared libraries actually resolve?
Read ldd output and recognise a 'not found' library as a broken dependency at the binary level.
Packages depend on each other; binaries depend on shared libraries in the same way. Most programs aren't self-contained — they call into
.so(shared object) files at runtime, the way a recipe calls for ingredients it assumes are already in your pantry. If an ingredient is missing, the dish fails the moment you try to cook it.ldd("list dynamic dependencies") prints every shared library a binary needs and where the linker found each one. If any library shows `not found`, the program won't run.ldd /usr/bin/treelinux-vdso.so.1 (0x00007ffe1c9f5000) libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f3a2b400000) /lib64/ld-linux-x86-64.so.2 (0x00007f3a2b6b0000)
Each line is library => resolved path. Herelibc.so.6resolves to a real file under/lib/..., so the binary's dependencies are satisfied.linux-vdso.so.1is a kernel-provided virtual library (no path is normal), andld-linux-x86-64.so.2is the dynamic linker itself.Now contrast that with a binary linked against a library the system can't locate:
Example — not runnable on your lab box
ldd /opt/acme/bin/acmetoollinux-vdso.so.1 (0x00007ffd4a3f1000) libacme.so.1 => not found libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f9c1d200000)
The line libacme.so.1 => not foundis the failure. The binary needslibacme.so.1, but the dynamic linker searched its known paths and the cache and came up empty. Runacmetoolnow and you'll geterror while loading shared libraries: libacme.so.1: cannot open shared object file. The library exists somewhere — but the system doesn't know where to look yet.Check yourself
In ldd output, what text next to a library name signals that the binary will fail to start because the library can't be located?
ldconfig: teach the linker where the new library lives
Fix a 'not found' library by registering its path and rebuilding the linker cache.
Why was
libacme.so.1'not found' even though the file exists? Because the dynamic linker doesn't scan the whole disk on every launch — that would be far too slow. Instead it consults a prebuilt index, `/etc/ld.so.cache`, listing libraries in known directories.That cache is built from the directories in `/etc/ld.so.conf` and the files dropped into `/etc/ld.so.conf.d/`. When you install a library into a new directory, you must (1) tell the system about that directory and (2) rebuild the cache with
ldconfig. Think ofldconfigas reindexing a library catalogue: the book is on the shelf, but until you update the catalogue card, the librarian insists it isn't there.echo '/opt/acme/lib' | sudo tee /etc/ld.so.conf.d/acme.conf/opt/acme/lib
Step 1: register the new directory. We add a small .conffile under/etc/ld.so.conf.d/containing the path wherelibacme.so.1actually lives.ldconfigwill read every.confhere. (teeis just a way to write the file with sudo.)sudo ldconfigStep 2: rebuild the cache. ldconfigrescans/etc/ld.so.conf, every file in/etc/ld.so.conf.d/, and the standard library directories, then regenerates `/etc/ld.so.cache`. Success is silent — no output means it worked.ldconfig -p | grep libacmelibacme.so.1 (libc6,x86-64) => /opt/acme/lib/libacme.so.1
- -p
- Print the current contents of the cache (/etc/ld.so.cache) — useful to confirm a library is now registered.
ldconfig -pdumps what the linker now knows about.libacme.so.1is in the cache pointing at/opt/acme/lib, so re-running the earlierldd /opt/acme/bin/acmetoolwould now resolve it instead of sayingnot found. The chain is complete: new path registered, cache rebuilt, library findable.Check yourself
You copied a new .so into /opt/acme/lib and added the path under /etc/ld.so.conf.d, but ldd still reports it 'not found'. What command rebuilds the cache so the linker sees it?
Try it in your live terminal
Do it, don't just read it — these safe, read-only commands run on the real sandbox to your right.
You have a real shell. These commands only read state — they install and remove nothing — so they're safe to run as-is. They work whether your sandbox is Debian-based or RPM-based.
ldd $(which ls)Inspect the shared libraries of a real binary ( ls). Every line should show a resolved path — nonot found. This is direction-free: just confirming a healthy binary.rpm -qf $(which bash) 2>/dev/null || dpkg -S $(which bash)File → Package direction: which package owns the bashbinary? Tries the RPM query first and falls back to the Debian query, so it answers on either distro family.ldconfig -p | headPeek at the linker cache: the first entries of every library ldconfigcurrently knows about. This is the very index anot foundfromlddmeans a library is missing from.
dpkginstalls exactly one.deband does no dependency resolution;aptreads the repos, resolves the whole dependency graph, andapt -f installrepairs the broken statedpkgleaves behind.dpkg-debreads a.debwithout installing it:-ffor control fields (Version,Depends),-cfor the files it ships,-Ifor the whole control file. OnlyDependsis a hard requirement;Recommendsis optional.apt-mark holdfreezes a package against upgrades (showholdlists,unholdreleases); settings go in/etc/apt/apt.conf.d/andapt-config dump KEYshows the value apt actually uses.- Querying has two opposite directions: owns a file (
dpkg -S/rpm -qf, path → package) versus lists files (dpkg -L/rpm -ql, package → paths). Never swap them. lddshows a binary's shared libraries and whether each resolves;not foundmeans the program won't start.- A new library in a new path is invisible until you register the path under
/etc/ld.so.conf.d/and runldconfigto rebuild/etc/ld.so.cache.
| Command | Family | Direction / effect |
|---|---|---|
dpkg -i pkg.deb | Debian | Install ONE .deb — no dependency resolution |
apt install pkg | Debian | Resolve dependencies from repos and install |
apt -f install | Debian | Repair broken / half-installed dependencies |
dpkg-deb -f pkg.deb Field | Debian | Read a control field from a .deb without installing it |
dpkg-deb -c pkg.deb | Debian | List the files a .deb would install |
apt-mark hold pkg | Debian | Freeze a package so upgrades skip it (showhold lists holds) |
apt-config dump KEY | Debian | Show apt's effective setting after every apt.conf.d file |
dpkg -S /path | Debian | Owns a file: path → package |
rpm -qf /path | RPM | Owns a file: path → package |
dpkg -L pkg | Debian | Lists files: package → paths |
rpm -ql pkg | RPM | Lists files: package → paths |
ldd /path/bin | Both | List a binary's shared libs; flag not found |
ldconfig | Both | Rebuild /etc/ld.so.cache from ld.so.conf.d |
