Signal9Signal9 — home
online
all lessons

LPIC-1 Package Management: dpkg/apt, rpm/dnf, and Shared Libraries

Beginner40 min9 steps
What you'll learn
  • 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
  1. 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 .deb file 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. dpkg is a person who will assemble exactly the one box you place in front of them. apt is 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?

  2. 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 .deb is just an archive: a control section (name, version, dependencies, description) plus the files it will drop onto your system. dpkg-deb is the tool that works on that archive directly. It never touches the package database, so it's how you vet a vendor's package before dpkg -i gets 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 --build turns a directory (a DEBIAN/control file 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-demo
    dpkg-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/true standing in for a real tool). Nothing is installed: you now hold hello-demo.deb in 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 Version
    1.2-1
    -f DEB FIELD...
    Print control fields. One field prints its bare value; several print Field: value lines. 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 Recommends
    Depends: 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 libc6 2.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.deb
    drwxr-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-demo installs 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, run dpkg-deb -I on the same file. Clean up with rm -r hello-demo hello-demo.deb.

    Check yourself

    Which dpkg-deb option lists the files inside a .deb without installing it?

  3. 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 .deb directly with dpkg -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.deb
    Selecting 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: dpkg unpacked the package, then stopped at configuration because libc6 isn'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; dpkg only knows about the one file you gave it.

    Check yourself

    After dpkg -i reports 'dependency problems - leaving unconfigured', what state is the package in?

  4. 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 apt can see the repositories, you can ask it to fix the broken state — it will find the missing dependency (libc6 here), download it, install it, and then finish configuring the package dpkg left hanging.

    sudo apt -f install
    Reading 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 install noticed 1 not fully installed, pulled in libc6, and then ran `Setting up tree` — finishing the exact configuration step dpkg had abandoned. The package is now fully installed. This is the standard recovery move after a manual .deb install goes wrong.

    Check yourself

    Which single command tells apt to find and install whatever dependencies are missing to repair a broken package?

  5. 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-mark sets it, lists it, and clears it.

    sudo apt-mark hold nano
    nano 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 upgrade reports nano under kept back instead of upgrading it. You can hold several packages in one command: apt-mark hold pkgA pkgB.
    apt-mark showhold
    nano
    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 nano
    Canceled 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 dump prints the merged, effective configuration; name a key to see just that key. The classic change is to stop apt pulling in every Recommends and Suggests on install:

    apt-config dump APT::Install-Recommends APT::Install-Suggests
    APT::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-Suggests
    APT::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 with apt-config dump. Both keys now say false. 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.conf to go back to the defaults.

    Check yourself

    Which command lists every package that is currently on hold?

  6. 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 use rpm -qf. Give it a path, get back the package.

    dpkg -S /usr/bin/tree
    tree: /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/tree is 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/tree
    tree-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 use rpm -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).

  7. 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/tree
    	linux-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. Here libc.so.6 resolves to a real file under /lib/..., so the binary's dependencies are satisfied. linux-vdso.so.1 is a kernel-provided virtual library (no path is normal), and ld-linux-x86-64.so.2 is 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/acmetool
    	linux-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 found is the failure. The binary needs libacme.so.1, but the dynamic linker searched its known paths and the cache and came up empty. Run acmetool now and you'll get error 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?

  8. 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 of ldconfig as 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 .conf file under /etc/ld.so.conf.d/ containing the path where libacme.so.1 actually lives. ldconfig will read every .conf here. (tee is just a way to write the file with sudo.)
    sudo ldconfig
    Step 2: rebuild the cache. ldconfig rescans /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 libacme
    	libacme.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 -p dumps what the linker now knows about. libacme.so.1 is in the cache pointing at /opt/acme/lib, so re-running the earlier ldd /opt/acme/bin/acmetool would now resolve it instead of saying not 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?

  9. 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 — no not 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 bash binary? Tries the RPM query first and falls back to the Debian query, so it answers on either distro family.
    ldconfig -p | head
    Peek at the linker cache: the first entries of every library ldconfig currently knows about. This is the very index a not found from ldd means a library is missing from.
Recap
  • dpkg installs exactly one .deb and does no dependency resolution; apt reads the repos, resolves the whole dependency graph, and apt -f install repairs the broken state dpkg leaves behind.
  • dpkg-deb reads a .deb without installing it: -f for control fields (Version, Depends), -c for the files it ships, -I for the whole control file. Only Depends is a hard requirement; Recommends is optional.
  • apt-mark hold freezes a package against upgrades (showhold lists, unhold releases); settings go in /etc/apt/apt.conf.d/ and apt-config dump KEY shows 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.
  • ldd shows a binary's shared libraries and whether each resolves; not found means 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 run ldconfig to rebuild /etc/ld.so.cache.
Command → direction / effect
CommandFamilyDirection / effect
dpkg -i pkg.debDebianInstall ONE .deb — no dependency resolution
apt install pkgDebianResolve dependencies from repos and install
apt -f installDebianRepair broken / half-installed dependencies
dpkg-deb -f pkg.deb FieldDebianRead a control field from a .deb without installing it
dpkg-deb -c pkg.debDebianList the files a .deb would install
apt-mark hold pkgDebianFreeze a package so upgrades skip it (showhold lists holds)
apt-config dump KEYDebianShow apt's effective setting after every apt.conf.d file
dpkg -S /pathDebianOwns a file: path → package
rpm -qf /pathRPMOwns a file: path → package
dpkg -L pkgDebianLists files: package → paths
rpm -ql pkgRPMLists files: package → paths
ldd /path/binBothList a binary's shared libs; flag not found
ldconfigBothRebuild /etc/ld.so.cache from ld.so.conf.d