Pacman, APT and dpkg: Under the Hood

/linux

Introduction

Package managers are often reduced to a few commands: install, update, remove.

But behind those commands sits a much larger system involving repositories, metadata, dependency resolution, local package databases, binary archives and source packages.

In this article, we will look at pacman, APT and dpkg to understand how these responsibilities are separated, how their toolchains are organized, and what actually happens between fetching a package and installing its files on the system.

pacman, the Arch familly

pacman is the default package managers of the Arch Linux familly (Arch Linux, Cachy OS, Garuda Linux...).

So first, let's decompose a pacman command, because we have primary flags describing the operation familly and secondary flags that will describe the command, and we can have arguments at the end.

Here's a quick summary of them:

  
  

-S    # sync
-R    # remove
-Q    # query local package database
-U    # upgrade/install a package file 
-F    # querying files database
-D    # package database manipulation, administrative metadata
-T    # versioning dependency test

  
  

the -S flag

The simplest use of this command is to download and install a package as:

  
  

pacman -S pkg

  
  

This will download the package into /var/cache/pacman/pkg under a compressed archive .package.tar.zst.

.zst is the extension of files compressed with Zstandard zstd.

For example:

  
  

zstd file.tar

  
  

produces:

  
  

file.tar.zst

  
  

And decompression:

  
  

unzstd file.tar.zst

  
  

or equivalently:

  
  

zstd -d file.tar.zst

  
  

Btw, here are the different extensions related to their compression format:

  • .gz -> gzip

  • .bz2 -> bzip2

  • .xz -> xz / LZMA2

  • .zst -> zstd

  • .zip -> ZIP format

Then -S pkg will install it into the system.

We can only download pkg into /var/cache/pacman/pkgusing the w secondary flag, so:

  
  

pacman -Sw pkg

  
  

And then later you could install that local package archive with:

  
  

sudo pacman -U /var/cache/pacman/pkg/pkg-<version>-x86_64.pkg.tar.zst

  
  

One particularly useful variant is:

  
  

pacman -Sp pkgA

  
  

which doesn't even download it: it prints the URL(s) pacman would use. So you can think of the progression as:

  • pacman -Sp pkgA -> tell me where it would come from

  • pacman -Sw pkgA -> download it, don't install it

  • pacman -S pkgA -> download + install it

Now, we'll use -S for updating.

First, we can use the y secondary flag to only update the repository databases:

  
  

sudo pacman -Sy

  
  

These synchronized repository databases are stored under:

  
  

/var/lib/pacman/sync/

  
  

They contain metadata about the packages currently available in the configured repositories, including their versions and dependencies.

After refreshing them, it is generally a good idea to proceed directly with the system upgrade, meaning downloading and installing newer versions of packages already installed on the system:

  
  

sudo pacman -Su

  
  

The reason we usually upgrade immediately after refreshing the databases is that Arch Linux does not support partial upgrades.

After pacman -Sy, pacman now knows about the newest state of the repositories, while the packages installed on our system may still belong to an older repository state.

This can cause current packages to break in the future.

For example, imagine that our system currently contains:

  
  

pkgA v1 -> depends on libX v1
libX v1

  
  

After refreshing the repository databases, the repositories may now contain:

  
  

pkgA v2 -> depends on libX v2
libX v2

  
  

If we refresh the databases but do not upgrade the system, and then install another package requiring libX v2 (for example pkgB v2), pacman may upgrade libX because it sees that the current installed version is too old but still leaves pkgA v1 installed because it's not its scope.

And "upgrade" means replacing the newer version by the new one, so effectively deleting the old versions some packages still have a depedency to.

We could therefore end up with something conceptually like:

  
  

pkgA v1 -> libX v1
             X no longer installed

pkgB v2 -> libX v2

  
  

This is why running:

  
  

sudo pacman -Sy

  
  

alone is generally discouraged.

Instead, the usual operation is:

  
  

sudo pacman -Syu

  
  

where:

  • S -> synchronization operation

  • y -> refresh repository databases

  • u -> upgrade installed packages

This keeps the installed system synchronized with the same repository state described by the newly downloaded databases.

Now imagine the following case:

I have A and B installed and both depends on libX v1.

But now I run:

  
  

pacman -Syu 

  
  

But it seems that B now depends on libX v2, but A still depends on libX v1.

What would happen, does 2 versions of the same package will stay installed to satisfied its dependents ?

Nope, in practice one of a few things happen:

  • the library maintains ABI compatibility, so both packages can use the newer libX

  • the distribution provides parallel library packages under different package names, such as libX1 and libX2

  • or pacman refuses the transaction because the dependency requirements cannot all be satisfied

For example, this is fine:

  
  

pkgA v2 -> libX v2

pkgB v4 -> libX v2

  
  

But this cannot normally coexist:

  
  

pkgA -> libX=1

pkgB -> libX=2

  
  

What's installed:

  
  

libX = ???

  
  

There is only one installed version of package libX.

There is, however, an important distinction between package versions and library .so names. A package called libX might contain something like:

  
  

/usr/lib/libX.so.2

  
  

and, during a transition, a compatibility package (it supposes it has another name) could provide:

  
  

/usr/lib/libX.so.1

  
  

Then both generations can coexist.

To search if a package exist in the configured repository, we do use the secondary s flag:

  
  

pacman -Ss pkg

  
  

We can also query package metadata from repos, for exmaple:

  
  

pacman -Si firefox

  
  

Outputs something like:

  
  

Repository      : extra
Name            : firefox
Version         : 143.0-1
Description     : Fast, Private & Safe Web Browser
Architecture    : x86_64
URL             : https://www.mozilla.org/firefox/
Licenses        : MPL-2.0
Groups          : None
Provides        : None
Depends On      : glibc  gtk3  libx11  libxcomposite  libxdamage
                  libxfixes  libxrandr  nss  nspr  dbus
Optional Deps   : hunspell: spell checking support
                  libnotify: desktop notifications
Conflicts With  : None
Replaces        : None
Download Size   : 78.42 MiB
Installed Size  : 265.31 MiB
Packager        : Arch Linux Package Maintainer <maintainer@archlinux.org>
Build Date      : Sun 13 Sep 2026 14:21:03
Validated By    : SHA-256 Sum  Signature

  
  

Conflicts With are all the packages pacman won't allow the queried package to coexist with.

The -R flag

That's the remove operation.

So first, the most used command will certainly be this one:

  
  

pacman -R pkg

  
  

Which will remove a pkg.

We could also add its configuration file with the secondary n (--nosave) flag:

  
  

pacman -Rn pkg

  
  

Indeed, without this flag, the package's configuration file that are tracked as backup files (Backup field that we'll see later on) and have been modified may be preserved by pacman by preserved by renaming them into something like:

  
  

/etc/pkg.someconf.pacsave

  
  

A usefull command is also this one:

  
  

pacman -Rdd pkg

  
  

This is usefull when pkg is a depedency.

Indeed, the first d flag tells pacman to ignore depedency version constraints.

For example:

  
  

A depends on foo=2

  
  

And we have:

  
  

pkgX
Provides: foo=2

pkgY
Provides: foo=1

  
  

Here pkgX and pkgY are different package names, so they can potentially coexist.

Normally, if we remove pkgX:

  
  

pacman -R pkgX

  
  

pacman sees:

  
  

A requires foo=2

  
  

So we have:

  
  

pkgX provided foo=2 <- being removed
pkgY provides foo=1 <- wrong version

  
  

and refuses.

But with:

  
  

pacman -Rd pkgX

  
  

pacman ignores the version constraint. The requirement is effectively checked as:

  
  

A requires foo

  
  

This also gives us an opportunity to look more closely at what we mean by a "dependency".

Suppose that A depends on a provision called libx.so, and that both pkg1 and pkg2 advertise that provision:

  
  

pkg1
Provides: libx.so

pkg2
Provides: libx.so

A
Depends on: libx.so

  
  

If both packages are installed, then:

  
  

pacman -R pkg1

  
  

can succeed because removing pkg1 does not leave the dependency unsatisfied: pkg2 still provides libx.so.

The important point is that pacman is not simply checking whether a file named libx.so physically remains somewhere on the filesystem. Dependency resolution is based on package metadata: package names, provides entries....

Indeed, pacman can represent dependencies on shared-library SONAMEs. For example, a package may provide:

  
  

lib:libx.so.2

  
  

While another package may depend on exactly:

  
  

lib:libx.so.2

  
  

Also, in that case, lib:libx.so.1 and lib:libx.so.2 are distinct dependency identities: providing one does not satisfy a dependency on the other.

So pacman supports several forms of dependency relations, including ordinary package dependencies and SONAME-based dependencies used to express shared-library requirements.

Now, we also have the s flag that removes recursively a package and its depedencies that are unneeded by others packages:

  
  

pacman -Rs pkg

  
  

If:

  
  

pkg -> A 
pkg -> B 
pkg -> C -> D
pkg2 -> B

  
  

If A, C and D were installed as depedencies, then removing pkg with the last command will remove them.

So -Rs recursively walks through the dependency graph, but only removes dependencies that are no longer required elsewhere and that are not marked as explicitly installed.

But to also removes depedencies that are marked as explicitely installed, we use second s flag:

  
  

pacman -Rss pkg

  
  

A depedency explicitely installed isn't an oxymoron

For example:

  
  

pacman -S A

  
  

We explicitly install A, so pacman records:

  
  

A
Install reason: Explicit

  
  

Later you install:

  
  

pacman -S B

  
  

and B happens to depend on A:

  
  

B -> A

  
  

Now A is simultaneously

  
  

A is a dependency of B

  
  

AND:

  
  

A is marked as explicitly installed

  
  

Those are perfectly compatible statements.

Indeed, we must distinct 2 type of "depedency" metadata; the reason why a package was installed (explicit or dep), and its role in the true depedency graph (nodep, dep, optdep).

Also, the optional depedencies of a package are not automatically installed with pacman -S, so if I have B installed that optionally depends on A, and that later I explicitely install A (with pacman -S A), then A is marked as explicitely installed and is an optional depedency of B.

Back to the -R flags.

A very common operation is combining n and s:

  
  

pacman -Rns pkg

  
  

The operation that goes in the opposite direction (in the depedency graph) of -Rs is -Rc.

Indeed, it'll go up in the dependency graph and remove what it traverses, but it doesn't take the optional depedency as relationship in the dep graph.

For example, if we have:

  
  

A (optional depedency) -> B -> C -> D

  
  

And that I do:

  
  

pacman -Rc D

  
  

Then D, C and B will be removed but not A.

The -Q flag

This is the flag to query informations on the local database.

For example, we got a nice symetry with -S for the i flag, indeed we also can retrieve informations on the installed package, for example:

  
  

pacman -Qi firefox

  
  

Outputs something like:

  
  

Name            : firefox
Version         : 143.0-1
Description     : Fast, Private & Safe Web Browser
Architecture    : x86_64
URL             : https://www.mozilla.org/firefox/
Licenses        : MPL-2.0
Groups          : None
Provides        : None
Depends On      : glibc  gtk3  libx11  libxcomposite  libxdamage
                  libxfixes  libxrandr  nss  nspr  dbus
Optional Deps   : hunspell: spell checking support
                  libnotify: desktop notifications
Required By     : None
Optional For    : some-other-package
Conflicts With  : None
Replaces        : None
Installed Size  : 265.31 MiB
Packager        : Arch Linux Package Maintainer <maintainer@archlinux.org>
Build Date      : Sun 13 Sep 2026 14:21:03
Install Date    : Mon 14 Sep 2026 09:42:17
Install Reason  : Explicitly installed
Install Script  : No
Validated By    : Signature

  
  

We also have the s variant flag to search for an installed package:

  
  

pacman -Qs firefox

  
  

A possible output:

  
  

local/firefox 143.0-1
    Fast, Private & Safe Web Browser

  
  

We can see depedencies with:

  
  

pacman -Qd

  
  

Possible output:

  
  

acl 2.3.2-1
at-spi2-core 2.56.3-1
brotli 1.1.0-3
cairo 1.18.4-1
dbus 1.16.2-1
expat 2.7.1-1
fontconfig 2:2.16.2-1
freetype2 2.13.3-1
glib2 2.84.4-1
graphite 1:1.3.14-4
harfbuzz 11.4.1-1
libpng 1.6.50-1
libx11 1.8.12-1
libxcb 1.17.0-1
pcre2 10.46-1
zlib 1:1.3.1-2

  
  

We can add the q flag to only have the names, which can be usefull in bash scripting for reasons we'll see later:

  
  

pacma,n -Qdq

  
  

Possible output:

  
  

acl 
at-spi2-core 
brotli 
cairo 
dbus 
expat 
fontconfig 
freetype2 
glib2 
graphite 
harfbuzz 
libpng 
libx11 
libxcb 
pcre2 
zlib 

  
  

It's also time to introduce the concept of optional dependencies more deeply.

An optional dependency describes a relationship between packages. For example:

  
  

pkgA --optdepends--> pkgB

  
  

This relationship is declared in pkgA's package metadata.

However, this relationship is completely separate from pkgB's installation reason.

Pacman records an installed package as either:

  
  

Explicitly installed

  
  

or:

  
  

Installed as a dependency

  
  

Therefore, a package can be marked as "installed as a dependency" even if its only current relationship with another installed package is an optional dependency.

For example, we can deliberately install pkgB with:

  
  

pacman -S --asdeps pkgB

  
  

Pacman then records:

  
  

pkgB
Install Reason: Installed as a dependency

  
  

while the dependency graph may contain only:

  
  

pkgA --optdepends--> pkgB

  
  

These two pieces of information describe different things:

  
  

pkgA --optdepends--> pkgB

  
  

describes the relationship between the packages, while:

  
  

pkgB: Install Reason = dependency

  
  

describes how pacman should classify pkgB on the local system.

Therefore, "installed as a dependency" does not necessarily mean that the package is currently a hard dependency of another installed package.

And with -Qd it only returns packages whose installation reason is depedency.

We can even download a package as a dependency but isn't used by any of installed package on the system.

The contrary to -d is the -e flag that tells pacman to return only explicitely marked packages:

  
  

pacman -Qe

  
  

Now, we must also have a way to inspect the graphs to list packages that are not required by any of the installed packages, it's in fact detecting orphans.

For that we use the -t flag.

Indeed, this will inspect the depedency flag and return exactly what we described.

  
  

pacman -Qt

  
  

A common options to clean a system is by removing its orphans, so a good command is:

  
  

pacman -Rns $(pacman -Qdtq)

  
  

Now, if you want to go a step further in the cleaning process, you can also mark the optional depedencies as needed to be remove, we do that by adding a t.

So the related command is:

  
  

pacman -Rns $(pacman -Qdttq)

  
  

We also have -Ql to list the files belonging to an installed package:

  
  

pacman -Ql glibc

  
  

Output:

  
  

glibc usr/
glibc usr/bin/
glibc usr/bin/ldd
glibc usr/include/
glibc usr/include/stdio.h
glibc usr/lib/
glibc usr/lib/libc.so.6
...

  
  

To get which installed package own a particular file, we do:

  
  

pacman -Qo path/to/some/file

  
  

May output something like:

  
  

/path/to/some/file is owned by coreutils 9.7-1

  
  

We won't have multiple matches for one file because pacman does not allow two installed packages to own the same filesystem path at the same time.

But we can give multiple files to the command:

  
  

pacman -Qo /usr/bin/ls /usr/bin/bash /usr/lib/libcrypto.so.3

  
  

May output something like:

  
  

/usr/bin/ls is owned by coreutils 9.7-1
/usr/bin/bash is owned by bash 5.3.3-1
/usr/lib/libcrypto.so.3 is owned by openssl 3.5.2-1

  
  

Also, if we are currently in:

  
  

/usr/bin

  
  

then:

  
  

pacman -Qo ls

  
  

can resolve to /usr/bin/ls.

But using the absolute path instead of relative is much clearer and less ambiguous.

We can also verify that a package is healthy with the -k (check) flag.

checks whether the files that belong to an installed package are still present on disk.

For example:

  
  

pacman -Qk openssl

  
  

might output:

  
  

openssl: 312 total files, 0 missing files

  
  

If some files are missing:

  
  

openssl: 312 total files, 2 missing files

  
  

With double k:

  
  

pacman -Qkk openssl

  
  

pacman performs a more detailed check, including file properties such as permissions, ownership, size, and modification time for files with metadata available.

For example:

  
  

warning: openssl: /usr/bin/openssl (Permissions mismatch)
warning: openssl: /usr/lib/libcrypto.so.3 (Size mismatch)
openssl: 312 total files, 2 altered files

  
  

So the distinction is roughly:

  • -Qk -> are package files present?

  • -Qkk -> are they present, and do their recorded metadata still match ?

The -F flag

This is for the file-package relation oriented operations.

The goal of this command is to provide a way to discover file(s)-package(s) relationship before even installing the package because it will ask the repositories servers to answer our questions.

Indeed, we can update the local databases containing the relationships under /var/lib/pacman/sync with the -y flag:

  
  

pacman -Fy

  
  

We can even force the update even if pacman thinks that all is still synced:

  
  

pacman -Fyy

  
  

As we saw earlier, the normal sync database refreshed by:

  
  

pacman -Sy

  
  

It's the repo.db side.

The file database is refreshed separately with the commands we just saw.

That separation makes sense because file lists are much larger. Pacman does not need the complete list of every file contained in every repository package just to resolve dependencies or install packages.

Now, we have the plain -F which will search for package(s) that contain the input filename, for example:

  
  

pacman -F libcrypto.so

  
  

May output something like:

  
  

core/openssl 3.5.2-1
    usr/lib/libcrypto.so

extra/openssl-1.1 1.1.1w-3
    usr/lib/openssl-1.1/libcrypto.so

extra/some-sdk 4.2-1
    opt/some-sdk/lib/libcrypto.so

  
  

We can also narrow the query by giving the absolute path.

And to be more flexible about the filename, we can even use RegEx with the -x flag, for example:

  
  

pacman -Fx 'libcrypto\.so(\.[0-9]+)*$'

  
  

May output something like:

  
  

core/openssl 3.5.2-1
    usr/lib/libcrypto.so
    usr/lib/libcrypto.so.3

extra/openssl-1.1 1.1.1w-3
    usr/lib/openssl-1.1/libcrypto.so.1.1

  
  

Btw, we can add the -q flag to flatten the output (like for -Qq):

  
  

pacman -Fqx 'libcrypto\.so(\.[0-9]+)*$'

  
  

Output:

  
  

openssl
openssl-1.1

  
  

--machinereadable flag make the result separated by null separators:

  
  

pacman -Fx --machinereadable 'libcrypto\.so(\.[0-9]+)*$'

  
  

Output:

  
  

repository\0package\0version\0path\n

  
  

Until now, we talked about the file(s) -> package(s) commands, but we also have a nice symetry with -Ql thanks to -Fl that allows to search for file(s) contained in a repository package:

  
  

pacman -Fl openssl

  
  

Could output:

  
  

openssl usr/
openssl usr/bin/
openssl usr/bin/openssl
openssl usr/include/
openssl usr/include/openssl/
openssl usr/lib/
openssl usr/lib/libcrypto.so
openssl usr/lib/libcrypto.so.3
openssl usr/lib/libssl.so
openssl usr/lib/libssl.so.3

  
  

And:

  
  

pacman -Flq openssl

  
  

Removes the package name and gives just the paths:

  
  

usr/
usr/bin/
usr/bin/openssl
usr/include/
usr/include/openssl/
usr/lib/
usr/lib/libcrypto.so
usr/lib/libcrypto.so.3
usr/lib/libssl.so
usr/lib/libssl.so.3

  
  

The -D flag

This command controls the metadata of a package, more precisely the reason why a package was installed, for example we can change a package that was installed as a depedency by another package as explicitely installed:

  
  

pacman -D --asexplicit dep

  
  

Or the opposite:

  
  

pacman -D --asdeps pkg

  
  

We can also check if the local package database against the installed system is healthy with the -k flag (a nice symetry to -Qk).

More precisely, it verifies things like: if required package files are present, installed dependencies are satisfied, installed packages do not conflict, and if multiple installed packages do not claim the same file.

  
  

pacman -Dk

  
  

On a healthy system, we may simply get:

  
  

No database errors have been found!

  
  

If something is wrong, we could conceptually see messages such as:

  
  

error: missing 'libfoo.so=2' dependency for 'pkgA'
error: file owned by 'pkgA' and 'pkgB': usr/lib/libfoo.so
error: required file missing for package 'pkgC'

  
  

Now:

  
  

sudo pacman -Dkk

  
  

does the same local consistency check and additionally checks the sync databases (repos) to make sure the dependencies declared by packages are available from the configured repositories.

For example, imagine our installed system has:

  
  

pkgA -> depends on libfoo

  
  

and libfoo is currently installed, so locally everything is fine.

Then:

  
  

pacman -Dk

  
  

Succeeds:

  
  

No database errors have been found!

  
  

But suppose none of our currently configured repositories contains libfoo anymore.

Then:

  
  

pacman -Dkk

  
  

could report a problem:

  
  

error: dependency 'libfoo' required by 'pkgA' is not available in sync databases

  
  

So conceptually:

  • -Dk -> check local installed state

  • -Dkk -> check local installed state + check whether required dependencies are available in the configured repositories

A nice example is a package installed from the AUR or from an old repository:

  
  

installed:
    pkgA
    libfoo

configured repositories:
    core
    extra

  
  

But libfoo no longer exists in either repository

But -Dkk additionally can flag it.

The -T flag

Its job is simple:

Take one or more dependency expressions and print only the ones that are not currently satisfied on the installed system.

For example:

  
  

pacman -T qt 'bash>=3.2'

  
  

If both dependencies are satisfied, pacman prints nothing and exits successfully.

If qt is missing but it satisfies "bash>=3.2", the output would be:

  
  

qt

  
  

If both are unsatisfied:

  
  

qt
bash>=3.2

  
  

We can test version constraints such as:

  
  

pacman -T 'foo=2'
pacman -T 'foo>=2'
pacman -T 'foo<=2'
pacman -T 'foo>2'
pacman -T 'foo<2'

  
  

A very useful scripting pattern is:

  
  

missing=$(pacman -T 'openssl>=3' zlib)

if [ -n "$missing" ]; then
    printf 'Missing dependencies:\n%s\n' "$missing"
fi

  
  

Indeed, in Bash -n checks if a string has a non-zero length:

  
  

SOMEVAR="yes"

[ -n $SOMEVAR ] && echo "$SOMEVAR" || echo "no"

  
  

Output:

  
  

yes

  
  

pactree

We install pactree with:

  
  

pacman -S pacman-contrib

  
  

pactree is basically a small depedency-graph explorer around pacman's database package.

To see the normal recursive depedency tree of a package (for example the ones -Rss will remove), we do:

  
  

pactree pkg

  
  

Possible output:

  
  

pkg
└─A
  └─B

  
  

To see the reverse depedency tree (the ones that depends on pkg), we do:

  
  

pactree -r pkg

  
  

Possible output:

  
  

pkg
└─C
  └─D

  
  

We can also limit depth with -d X where X is an integer, for example:

  
  

pactree -d 1 -r pkg 

  
  

Possible output:

  
  

pkg
└─C

  
  

Because in a graph we can have something like:

  
  

  A
 /\
B  C
 \/
 D
 
  
  

So if I do:

  
  

pactree A

  
  

I have:

  
  

A
└─B
| └─D
└─C
  └─D

  
  

So if I want to only list unique depedencies, I do:

  
  

pactree -u A

  
  

Then, I only get:

  
  

A
B
C
D

  
  

Until now, we only listed hard depedencies, but we can also include optional depedencies in the results with the -o flag.

So if:

  
  

A -> B
A --optdepends--> C

  
  

Then:

  
  

pactree -o pkg

  
  

Includes C.

We can couple that with -r to ask either hard-depend or optionally depends on the input package:

  
  

pactree -u -r -o pkg

  
  

We can even use the above commands on packages we did not even installed, just by looking into the local metadata package database thanks to the -s flag.

For example, to see the depedency graph of firefox and that we've not yet installed it, we can do:

  
  

pactree -s firefox

  
  

But it's preferable to update the local metadata package datababase first with:

  
  

pacman -Sy

  
  

Because pactree -s pkg does not directly ask the repos.

We can even generate a real SVG or PNG image of a package depedency graph thanks to the combination of pactree -g somepkg and the dot command provided by GraphViz.

So, we install GraphViz with:

  
  

pacman -S graphviz

  
  

Now we can generate the file necessary to GrapghViz:

  
  

pactree -g pkg > pkg.dot

  
  

This file contains DOT language content, something like:

DOT is specifically a language for describing graphs with nodes and edges.

  
  

digraph G {
    "pkg" -> "libA";
    "pkg" -> "libB";
    "libB" -> "libC";
}

  
  

And now we provide its content to the dot command as:

  
  

dot -Tsvg pkg.dot -o pkg.svg

  
  

to obtain the SVG representing the dep graph of pkg.

For others image formats, we have:

  
  

-Tpng
-Tjpeg
-Tjpg
-Tpdf
...

  
  

Cf: man dot.

We can also directly provides stdout of the pactree command to the stdin of the dot command:

  
  

pactree -g pkg | dot -Tsvg -o pkg.svg

  
  

apt & dpkg, the Debian familly

apt and dpkg are the default package managers of the Debian familly (Debian, Ubuntu, Linux Mint...).

Architecture

In fact, we have 2 layers.

apt is the higher-level package manager that understands repositories, dependency resolution, upgrades, and transactions.

dpkg is the low-level local package manager. It knows how to install/remove/configure .deb packages and maintain the local package database.

The equivalent of /var/lib/pacman/sync is /var/lib/apt/lists.

The files in /var/lib/apt/lists contain packages metadata (one file per source containing all its available packages) such as packages names, versions, dependencies, descriptions, architecture, and the repository path to their related .deb. They are called package indexes files.

For example, we can search the Bash package metadata in the local synced metadata database (for the noble source, the one making it available):

  
  

grep -A 25 '^Package: bash$'   /var/lib/apt/lists/*noble*Packages

  
  

I get the output for the amd64 and the i386 CPU architecture:

  
  

/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_noble_main_binary-amd64_Packages:Package: bash
/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_noble_main_binary-amd64_Packages-Architecture: amd64
/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_noble_main_binary-amd64_Packages-Version: 5.2.21-2ubuntu4
/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_noble_main_binary-amd64_Packages-Multi-Arch: foreign
/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_noble_main_binary-amd64_Packages-Priority: required
/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_noble_main_binary-amd64_Packages-Essential: yes
/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_noble_main_binary-amd64_Packages-Section: shells
/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_noble_main_binary-amd64_Packages-Origin: Ubuntu
/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_noble_main_binary-amd64_Packages-Maintainer: Ubuntu Developers <ubuntu-devel-discuss@lists.ubuntu.com>
/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_noble_main_binary-amd64_Packages-Original-Maintainer: Matthias Klose <doko@debian.org>
/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_noble_main_binary-amd64_Packages-Bugs: https://bugs.launchpad.net/ubuntu/+filebug
/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_noble_main_binary-amd64_Packages-Installed-Size: 1900
/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_noble_main_binary-amd64_Packages-Pre-Depends: libc6 (>= 2.38), libtinfo6 (>= 6)
/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_noble_main_binary-amd64_Packages-Depends: base-files (>= 2.1.12), debianutils (>= 5.6-0.1)
/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_noble_main_binary-amd64_Packages-Recommends: bash-completion
/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_noble_main_binary-amd64_Packages-Suggests: bash-doc
/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_noble_main_binary-amd64_Packages-Filename: pool/main/b/bash/bash_5.2.21-2ubuntu4_amd64.deb
/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_noble_main_binary-amd64_Packages-Size: 794086
/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_noble_main_binary-amd64_Packages-MD5sum: 627cdbb775b1a60dadd502e96e0426b1
/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_noble_main_binary-amd64_Packages-SHA1: 8065b79389fc555b38cf71e297a259773b09c38e
/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_noble_main_binary-amd64_Packages-SHA256: 73de311a21e094e29ac01527d2b52226cc87fde0a5b57032902251b426d92c66
/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_noble_main_binary-amd64_Packages-SHA512: c14c2c8fa0d1ae7530efa0375845257b4ec0baa5bc42982efca2d7fa860b4e4ea3f04d31cfa328d2f250b2baeac3795c8b31aa5ff2d514ed16a5cf8c3132a590
/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_noble_main_binary-amd64_Packages-Homepage: http://tiswww.case.edu/php/chet/bash/bashtop.html
/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_noble_main_binary-amd64_Packages-Description: GNU Bourne Again SHell
/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_noble_main_binary-amd64_Packages-Task: minimal
/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_noble_main_binary-amd64_Packages-Description-md5: 3522aa7b4374048d6450e348a5bb45d9
--
/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_noble_main_binary-i386_Packages:Package: bash
/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_noble_main_binary-i386_Packages-Architecture: i386
/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_noble_main_binary-i386_Packages-Version: 5.2.21-2ubuntu4
/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_noble_main_binary-i386_Packages-Multi-Arch: foreign
/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_noble_main_binary-i386_Packages-Priority: required
/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_noble_main_binary-i386_Packages-Essential: yes
/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_noble_main_binary-i386_Packages-Section: shells
/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_noble_main_binary-i386_Packages-Origin: Ubuntu
/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_noble_main_binary-i386_Packages-Maintainer: Ubuntu Developers <ubuntu-devel-discuss@lists.ubuntu.com>
/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_noble_main_binary-i386_Packages-Original-Maintainer: Matthias Klose <doko@debian.org>
/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_noble_main_binary-i386_Packages-Bugs: https://bugs.launchpad.net/ubuntu/+filebug
/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_noble_main_binary-i386_Packages-Installed-Size: 1872
/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_noble_main_binary-i386_Packages-Pre-Depends: libc6 (>= 2.38), libtinfo6 (>= 6)
/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_noble_main_binary-i386_Packages-Depends: base-files (>= 2.1.12), debianutils (>= 5.6-0.1)
/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_noble_main_binary-i386_Packages-Recommends: bash-completion
/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_noble_main_binary-i386_Packages-Suggests: bash-doc
/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_noble_main_binary-i386_Packages-Filename: pool/main/b/bash/bash_5.2.21-2ubuntu4_i386.deb
/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_noble_main_binary-i386_Packages-Size: 766700
/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_noble_main_binary-i386_Packages-MD5sum: 812906d1bb2b172d44ef9152a077ffa9
/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_noble_main_binary-i386_Packages-SHA1: 6354f03b8937bfb23a1ab1df054024a0d1395d5f
/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_noble_main_binary-i386_Packages-SHA256: f1e68b898f37325c94798b4e02fdcbe1c24995c4c2680dbc46d33fc843f3486f
/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_noble_main_binary-i386_Packages-SHA512: 12d7d0d38088870b975f7ead08345fe08d0ea6494e6d68456ca8280d988647386c796569a40a66718c2bcdc7d106cfd7a04a6c104ef754721a64127de4a31ae1
/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_noble_main_binary-i386_Packages-Homepage: http://tiswww.case.edu/php/chet/bash/bashtop.html
/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_noble_main_binary-i386_Packages-Description: GNU Bourne Again SHell
/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_noble_main_binary-i386_Packages-Task: minimal
/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_noble_main_binary-i386_Packages-Description-md5: 3522aa7b4374048d6450e348a5bb45d9

  
  

Btw, I printed out the next 25 lines above the line where the match has been made thanks to -A 25.

Here, with the filenames containing the results, I know that Bahs comes from the https://archive.ubuntu.com/ubuntu repo, and the Filename for amd64 for example is:

  
  

pool/main/b/bash/bash_5.2.21-2ubuntu4_amd64.deb

  
  

Therefore, the URL it'll be downloaded is:

  
  

https://archive.ubuntu.com/ubuntu/pool/main/b/bash/bash_5.2.21-2ubuntu4_amd64.deb

  
  

From the metadata filename we also see the distribution sources it comes from, in that case it's from:

  
  

deb http://archive.ubuntu.com/ubuntu noble main

  
  

Which appears in /etc/apt/sources.list.d/official-package-repositories.list alongside with restricted, universe and multiverse variants:

  
  

deb http://archive.ubuntu.com/ubuntu noble main restricted universe multiverse

  
  

Now, back to the metadata.

Interrestings fields are the one describing the edges of the depedency graph.

Pre-Depends is stronger than a normal Depends; those packages must already be configured before the targeted package itself is unpacked/configured.

Depends are the ordinary hard dependencies, they are installed in the targeted package installation process.

Recommends are weaker than hard dependencies but, by default, apt usually installs them.

Suggests are weaker and are normally not installed automatically.

Those are metadata stored in the indexes files describing the packages relationship.

But, such as for pacman, the metadata concerning the reason why a package was installed also exists, for that we only have manualand automatic.

Also, like for the pacman metadata we have the Installed-Size of the package and the Size of the .deb file.

And the equivalent of /var/cache/pacman/pkg is /var/cache/apt/archives (stagging dir).

Where the .deb (the files actually containing the package) are temporary downloaded to be installed (see later).

The .deb format

A .deb file is a Debian-familly-specific structure.

Indeed, a modern .deb is essentially an ar archive containing three main members, conceptually:

  
  

package.deb
├── debian-binary
├── control.tar.*
└── data.tar.*

  
  

ar means archiver. It is a very simple container format.

tar, by contrast, was designed to archive a filesystem tree while preserving file-oriented metadata such as paths, directory structure, permissions, ownership, timestamps, symlinks, etc.

So inside the .deb, Debian uses tar for the parts that actually need to represent directory trees like control.tar and data.tar.

debian-binary is a tiny text file describing the .deb format version, for example:

"This archive uses Debian binary package format version 2.0."

control.tar.* contains the package’s own control metadata and maintainer scripts.

Typical contents look like:

  
  

control.tar.xz
├── control
├── md5sums
├── preinst   |
├── postinst  |__ maintainer scripts
├── prerm     |
└── postrm    |

  
  
  • preinst -> run before the package’s files are unpacked/installed.

  • postinst -> run after the package has been unpacked, typically to finish configuration.

  • prerm -> run before removing or replacing the package’s installed files.

  • postrm -> run after removal; may also run during purge/upgrade-related cleanup.

The important one is:

  
  

control

  
  

That file contains metadata such as:

  
  

Package: bash
Version: 5.2.21-2ubuntu4
Architecture: amd64
Depends: ...
Recommends: ...
Description: ...

  
  

So it resembles what we saw in:

  
  

/var/lib/apt/lists/...

  
  

data.tar.* contains the actual filesystem payload: binaries, libraries, docs, config files, and so on. The inner tar archives may be compressed with gzip, xz, zstd, or left uncompressed, depending on the package/tooling.

The apt install command

This si the direct equivalent of pacman -S, to install a package we do:

  
  

apt install pkg

  
  

Here, APT mainly resolves pkg using the local repository metadata.

We can of course specify the exact version to install:

  
  

apt install bash=5.2.21-2ubuntu4

  
  

or target a specific download suite:

  
  

apt install bash/noble

  
  

or with this form:

  
  

apt install -t noble bash

  
  

And to combine both we do:

  
  

apt install -t noble bash=5.2.21-2ubuntu4

  
  

With Pacman, we can only precise the repository:

  
  

sudo pacman -S core/bash

  
  

or:

  
  

sudo pacman -S extra/pkg

  
  

That's why we have the opportunity to download an arbitrary .tar.zst file and directly download the inner package with pacman -U.

When the package is already installed but there's a newer version available, then it upgrades it.

We also can force to reinstall the package with the --reinstall flag, that's the equivalent of what pacman -S natively does, by the way I knwo we are in the apt part, but to skip reinstalling, we can also do:

  
  

pacman -S --needed pkg

  
  

Pacman has a similar behavior. But as discussed earlier in the pacman part; refreshing with pacman -Sy and after pacman -S pkg can cause a partial upgrade, so that's better to a full upgrade pacman -Syu.

We also can pass multiple packages to this command such as:

  
  

apt install pkg1 pkg2 ...

  
  

That's the case for most "scoped" operations such as remove, purge, download...

And we can also download a package without installing it.

The apt download command

Indeed, withthis command we directly download the .deb in the current working directory:

  
  

apt download pkg

  
  

So I get its latest .deb file known to my local database.

I can now also use the apt install command to install the manually downloaded .deb file onto my system:

  
  

apt install pkg.deb

  
  

The apt update command

In order to update the indexes files under (/var/lib/apt/lists), we do:

  
  

apt update

  
  

That's a good moment to speak more about the architecture under /var/lib/apt/lists.

Indeed, until now we spoke about a specific type of indexes files, that is the Packages files.

It contains one stanza per binary package/version/architecture, with fields like Package, Version, Depends, Filename, Size, hashes like we saw before etc. It is what APT primarily uses to resolve and download .deb packages.

But, we also have the InRelease files for the repos.

It describes the repository release itself and is cryptographically signed. Think of it as the authenticated manifest for a suite such as noble or noble-updates. It includes information about the suite and checksums for index files beneath it. Older layouts can use:

  
  

Release
Release.gpg

  
  

instead of the combined signed InRelease.

For example, on my systemI have:

  
  

ls /var/lib/apt/lists/*InRelease*

  
  

Output:

  
  

/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_noble-backports_InRelease
/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_noble_InRelease
/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_noble-updates_InRelease
/var/lib/apt/lists/brave-browser-apt-release.s3.brave.com_dists_stable_InRelease
/var/lib/apt/lists/dl.google.com_linux_chrome-stable_deb_dists_stable_InRelease
/var/lib/apt/lists/download.docker.com_linux_ubuntu_dists_noble_InRelease
/var/lib/apt/lists/download.virtualbox.org_virtualbox_debian_dists_noble_InRelease
/var/lib/apt/lists/ppa.launchpadcontent.net_neovim-ppa_stable_ubuntu_dists_noble_InRelease
/var/lib/apt/lists/ppa.launchpadcontent.net_obsproject_obs-studio_ubuntu_dists_noble_InRelease
/var/lib/apt/lists/repository.mullvad.net_deb_stable_dists_stable_InRelease
/var/lib/apt/lists/security.ubuntu.com_ubuntu_dists_noble-security_InRelease

  
  

Corresponding to sources configured in /etc/apt/sources.list.d.

And in /var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_noble-backports_InRelease I have:

  
  

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA512

Origin: Ubuntu
Label: Ubuntu
Suite: noble-backports
Version: 24.04
Codename: noble
Date: Fri, 18 Sep 2026  9:51:49 UTC
Architectures: amd64 arm64 armhf i386 ppc64el riscv64 s390x
Components: main restricted universe multiverse
Description: Ubuntu Noble Backports
NotAutomatic: yes
ButAutomaticUpgrades: yes
MD5Sum:
 5411412b905e9e1e02916ec0392ba99b         13983948 Contents-amd64
 f1230d81f3b242467fdd49e351c525d6           787282 Contents-amd64.gz
 9fd34264f8d3897914557b9e745dbf8c         13984006 Contents-arm64
 db8a6c1774a31b89a098055826a0e4a5           787237 Contents-arm64.gz
 fcdb2d8f5cc59ff6f2ab04d84a69f97f         13993054 Contents-armhf
 764137197933b155e59adc12739d1537           787556 Contents-armhf.gz
 1a23e10fbaeacdc7eee59e3b17df9717         13692747 Contents-i386
 0eb4d420a431ccbd265dbd3c80389ed1           765737 Contents-i386.gz
 6afbcd30d6511235f41f424c962db54b         13845889 Contents-ppc64el
 9cc95b8b57d92500b9f196e233440ce2           778306 Contents-ppc64el.gz
 ebd7a9b106dd37f2a33e55a41a621870         13834906 Contents-riscv64
 4ad86b0910edacb6840467b99149e2cb           777758 Contents-riscv64.gz
 14d11d791d530764db819caf4ca49b08         13977389 Contents-s390x
 cd9784cfa9798f50acc5a8291929d72d           786595 Contents-s390x.gz
 452bb7eaff9030d56be9267049aa2c90           458763 main/binary-amd64/Packages
 e7bc1964980b200d652cfb41df5c5615            78515 main/binary-amd64/Packages.gz
 ced56a0198f9cf3ed46195c2f36057f6            65852 main/binary-amd64/Packages.xz
...

SHA1:
 d90f92d8858d50ad998968e50a397fc96984f03f         13983948 Contents-amd64
 70e2d32d2b0b1609f92dbd5c67a265e09ca257ab           787282 Contents-amd64.gz
 047ce43d4159df4ca8635bccf8a32752b8ae5f4d         13984006 Contents-arm64
 a79430c001a7e87d34a3a4cba9e0774334132d66           787237 Contents-arm64.gz
 bbe4a598b4f5742c500b7ffc2685c70946af9045         13993054 Contents-armhf
 e746bba5d0e19a54027a755ef985b6ab1b0548ed           787556 Contents-armhf.gz
 a0c55ed52dcd51fc01442f4c9cb49cf3f3f5d061         13692747 Contents-i386
 a7ec7faa2f90f982d81248a31d2fef1f5ae200e2           765737 Contents-i386.gz
 aa30ac4faca6d31d2dbb462c99dcfc741961c231         13845889 Contents-ppc64el
 7639db0a4210479ec8efd83e7bcb874f100eab01           778306 Contents-ppc64el.gz
 3de1e19cf52bdb9b9f719ba5912dd0197c1aa153         13834906 Contents-riscv64
 a7665556b9624e392972ebea5895b3dec40434b9           777758 Contents-riscv64.gz
 2162f6a4ee66a80cf352ceb63be33aef5f29f001         13977389 Contents-s390x
 da052001e1ca53c94c2461a9c40b43971ad89a62           786595 Contents-s390x.gz
 190aba181cb538c7404e310952d0ada102badbca           458763 main/binary-amd64/Packages
 dd0e83aaf3172cb1c06593137f03739f43cc5c05            78515 main/binary-amd64/Packages.gz
 92b72275f907330db4ca0937ac4753a02aa3cbe2            65852 main/binary-amd64/Packages.xz
 ...
SHA256:
 1b74788a0e65cf17c069a8f64017f889a7e8fb41d043033f91b08e52d172d5d0         13983948 Contents-amd64
 b8bc378605901b9918ec0e8f0dc3bd4e111407528bdb896c6e52708e6d8e2f17           787282 Contents-amd64.gz
 ed54771351ccfaf8f9cb9c5c7ef6f528779ae7a412a5e83c48a020382a0febbd         13984006 Contents-arm64
 17da1af7a1f5727ceb8930ea5dc1f81fa594f9428bec2ee5148351baac1f85e3           787237 Contents-arm64.gz
 283a4b39027087b213b39b9213b971bee36ea43aca1c18a45103810f156a8876         13993054 Contents-armhf
 c374cc2dd01b8c3022845c5144c02a6aac32c0fc54b0545e50061e949828b2c5           787556 Contents-armhf.gz
 3ee766fadc39d64e0802ab7a143b9f3b81164ce7150884199ed788fba61b3d00         13692747 Contents-i386
 0338098492149f076108f4b5f4c82cc997ad323b385cdab98bcfb0f00a2621f5           765737 Contents-i386.gz
 888238e91efbeb68dddc997b1f62e8c2d5a3eb9f70e7f696f17f31e611a62b2c         13845889 Contents-ppc64el
 bcd092cd2fe82a394ec90cbcb54ebb365ee17571ca3c8ba9b68a764a0c6853d0           778306 Contents-ppc64el.gz
 653690d5e651fc8dcefefe4abbb1a4a7e735eca1098c52a74e2c9da504fb4913         13834906 Contents-riscv64
 aa6c41006f788499aa3b296ccc4385cabab14cd0a1e4351e84e9011adb3785ca           777758 Contents-riscv64.gz
 233286559029e010672d6afef1bb23b556891d9a044a46fb872e7cf2c6c1e8dd         13977389 Contents-s390x
 b45e59afe297e4436dc851e5b50317516f04da82403aa879f9cce2007bbb497a           786595 Contents-s390x.gz
 0dbf7352fd30157619fb060ff2e39143fde22f0017b2a09b249bdd297c7c31fe           458763 main/binary-amd64/Packages
 ...
Acquire-By-Hash: yes
-----BEGIN PGP SIGNATURE-----

...

-----END PGP SIGNATURE-----

  
  

The key is that there are two different hash layers, and they protect different things.

In our InRelease, lines like:

  
  

MD5Sum:
 452bb7eaff9030d56be9267049aa2c90  458763 main/binary-amd64/Packages
 e7bc1964980b200d652cfb41df5c5615   78515 main/binary-amd64/Packages.gz
 ced56a0198f9cf3ed46195c2f36057f6   65852 main/binary-amd64/Packages.xz

  
  

are not hashes of individual .deb packages.

They are hashes of the repository index files themselves:

  
  

main/binary-amd64/Packages
main/binary-amd64/Packages.gz
main/binary-amd64/Packages.xz
Contents-amd64
...

  
  

Here, the filenames does not necessarly match the local indexes files on the system because the remote repository currently exposes the index such as:

  
  

dists/noble/main/binary-i386/Packages.gz
dists/noble/main/binary-i386/Packages.xz

  
  

(for reconstructing the URL where they are exposed)

APT downloads those compressed indexes, verifies them against the hashes recorded in the related InRelease, then may store the decompressed result locally as:

  
  

..._main_binary-i386_Packages

  
  

So searching the InRelease for the literal local filename won’t work.

Back to the architecture of a InRelease file:

Each hash line is basically:

  
  

HASH                  SIZE      FILE

  
  

The signed InRelease therefore authenticates those indexes.

Then inside the authenticated Packages file, we have another layer:

  
  

Package: bash
...
Filename: pool/main/b/bash/bash_5.2.21-2ubuntu4_amd64.deb
Size: 794086
SHA256: 73de311a21e094e29ac01527d2b52226cc87fde0a5b57032902251b426d92c66
SHA512: ...

  
  

Those hashes are the hashes of the individual .deb file.

There is also Translation files.

They contain translated package descriptions. Instead of duplicating translated descriptions inside every architecture-specific Packages index, repositories can distribute separate translation indexes such as:

  
  

Translation-en
Translation-fr

  
  

I can see them for example:

  
  

ls /var/lib/apt/lists/*Translation*

  
  

Output:

  
  

/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_noble-backports_main_i18n_Translation-en
/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_noble-backports_multiverse_i18n_Translation-en
/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_noble-backports_universe_i18n_Translation-en
/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_noble_main_i18n_Translation-en
/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_noble_main_i18n_Translation-fr
/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_noble_multiverse_i18n_Translation-en
/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_noble_multiverse_i18n_Translation-fr
/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_noble_restricted_i18n_Translation-en
/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_noble_restricted_i18n_Translation-fr
/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_noble_universe_i18n_Translation-en
/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_noble_universe_i18n_Translation-fr
/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_noble-updates_main_i18n_Translation-en
/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_noble-updates_multiverse_i18n_Translation-en
/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_noble-updates_restricted_i18n_Translation-en
/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_noble-updates_universe_i18n_Translation-en
/var/lib/apt/lists/ppa.launchpadcontent.net_neovim-ppa_stable_ubuntu_dists_noble_main_i18n_Translation-en
/var/lib/apt/lists/ppa.launchpadcontent.net_obsproject_obs-studio_ubuntu_dists_noble_main_i18n_Translation-en
/var/lib/apt/lists/security.ubuntu.com_ubuntu_dists_noble-security_main_i18n_Translation-en
/var/lib/apt/lists/security.ubuntu.com_ubuntu_dists_noble-security_multiverse_i18n_Translation-en
/var/lib/apt/lists/security.ubuntu.com_ubuntu_dists_noble-security_restricted_i18n_Translation-en
/var/lib/apt/lists/security.ubuntu.com_ubuntu_dists_noble-security_universe_i18n_Translation-en

  
  

Now, Contents indexes answer a different question:

“Which repository package contains this file?”

For example, apt-file search foo.h searches repository Contents indexes. This is why apt-file is roughly the Debian equivalent of pacman -F.

Indeed, after installing apt-file with:

  
  

apt install apt-file 

  
  

And updating the system to have the Contents files:

  
  

apt update

  
  

Then, we'll see what's concretely a Contents file, so I take one and decompress one.

  
  

cp /var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_noble_Contents-i386.lz4 test_Contents.lz4

lz4 -d test_Contents.lz4 test_Contents

  
  

And we can finally look into it.

In fact it's just a simple file-path -> package name (of the related source) mapping.

Here's a sample:

  
  

bin/btrfs						    admin/btrfs-progs
bin/btrfs-convert					    admin/btrfs-progs
bin/btrfs-find-root					    admin/btrfs-progs
bin/btrfs-image						    admin/btrfs-progs
bin/btrfs-map-logical					    admin/btrfs-progs
bin/btrfs-select-super					    admin/btrfs-progs
bin/btrfsck						    admin/btrfs-progs
bin/btrfstune						    admin/btrfs-progs
bin/cgroups-mount					    universe/admin/cgroup-lite
bin/cgroups-umount					    universe/admin/cgroup-lite
bin/ed							    editors/ed
bin/fusermount						    utils/fuse3,universe/utils/fuse
bin/fusermount3						    utils/fuse3
bin/ip							    net/iproute2

  
  

Now, there are the cnf_Commandsindexes files.

Those are the files used by the Ububntu "Command notfound" system.

Their purposes is to map shell command names to the packages that provide them.

Here's a list of the cnf_Commands indexes files I have on my system:

  
  

ls /var/lib/apt/lists/*cnf*

  
  

Output:

  
  

/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_noble-backports_main_cnf_Commands-amd64
/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_noble-backports_multiverse_cnf_Commands-amd64
/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_noble-backports_restricted_cnf_Commands-amd64
/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_noble-backports_universe_cnf_Commands-amd64
/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_noble_main_cnf_Commands-amd64
/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_noble_multiverse_cnf_Commands-amd64
/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_noble_restricted_cnf_Commands-amd64
/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_noble_universe_cnf_Commands-amd64
/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_noble-updates_main_cnf_Commands-amd64
/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_noble-updates_multiverse_cnf_Commands-amd64
/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_noble-updates_restricted_cnf_Commands-amd64
/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_noble-updates_universe_cnf_Commands-amd64
/var/lib/apt/lists/security.ubuntu.com_ubuntu_dists_noble-security_main_cnf_Commands-amd64
/var/lib/apt/lists/security.ubuntu.com_ubuntu_dists_noble-security_multiverse_cnf_Commands-amd64
/var/lib/apt/lists/security.ubuntu.com_ubuntu_dists_noble-security_restricted_cnf_Commands-amd64
/var/lib/apt/lists/security.ubuntu.com_ubuntu_dists_noble-security_universe_cnf_Commands-amd64

  
  

And for example, in:

  
  

/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_noble_main_cnf_Commands-amd64

  
  

I have this structure:

  
  

suite: noble
component: main
arch: amd64


name: acct
version: 6.6.4-5
commands: ac,accton,dump-acct,dump-utmp,lastcomm,sa

name: acl
version: 2.3.1-3
commands: chacl,getfacl,setfacl

name: acpid
version: 1:2.0.34-1ubuntu1
commands: acpi_listen,acpid

name: adcli
version: 0.9.2-1ubuntu1
commands: adcli

  
  

The first block (header) just describes to which repo source it belongs to.

Now, the body is composed of multiple blocks.

One block describes the package, its version and the commands it provides.

The apt upgrade command

This is just the command to install the package(s) APT sees a newer version for by comparing the current installed version to the related one in its database.

It will temporally download the associated .deb file in /var/cache/apt/archives and install it from there.

We also have the full-upgrade equivalent that is allowed to remove others installed package(s) is that's necessary to complete the installation.

Indeed, we can summarize the distinction as:

  
  

apt upgrade
-> upgrades packages
-> may install new dependencies
-> avoids removing already-installed packages

apt full-upgrade
-> upgrades packages
-> may install new dependencies
-> may remove other packages if dependency changes require it

  
  

The key distinction is other packages. Because of course a normal upgrade, will for example remove the old pkgA to replace it with its newer version. But what about conflicts between pkgA and pkgB ?

Take this example:

  
  

installed:
pkgA 1.0
pkgB 1.0

  
  

And:

  
  

new repository state:
pkgA 2.0 Depends: pkgC
pkgA 2.0 Conflicts: pkgB

  
  

With:

  
  

sudo apt upgrade

  
  

APT may hold A back, because upgrading it would require removing pkgB.

With:

  
  

sudo apt full-upgrade

  
  

APT is allowed to choose:

  
  

remove pkgB
install pkgC
upgrade pkgA → 2.0

  
  

So full-upgrade gives APT more freedom to reshape the dependency graph.

Pacman does not have an upgrade vs full-upgrade split like APT.

If a package transition requires another package to be replaced or removed because of conflicts / replaces, pacman can handle that as part of the transaction, usually prompting us.

The apt remove command

This is the equivament of pacman -R pkg, we just run:

  
  

apt remove pkg

  
  

The associated config files are not removed neither its depedencies.

The apt purge command

This is the command that removes a package and its config files, or just its config files if already removed (see the example).

So this is the standard way:

  
  

apt purge pkg

  
  

But imagine we already removed the package (with apt remove pkg) and now we want to remove its config files, will the purge work ?

Yes.

You can just run:

  
  

apt purge pkg

  
  

Indeed, with just:

  
  

apt remove pkg

  
  

Its package-managed configuration files may remain. dpkg can keep the package in a state often described as “removed config-files remain”.

We can see such packages with:

  
  

dpkg -l

  
  

To list installed packages and their states (see later).

where the status may begin with:

  
  

rc

  
  

meaning roughly:

  
  

r = removed
c = config files remain

  
  

Pacman does not provide a way to remove a package's config files after removing it, we must directly use pacman -Rn pkg.

But because with pacman -R pkg, the associated config files are often renamed with the .pacsave extensions, we can just list them and remove them:

  
  

find /etc/ -name '*.pacsave' -exec rm {} +

  
  

The apt autoremove command

This command is the closest to:

  
  

pacman -Rns $(pacman -Qdtq)

  
  

Telling to remove packages that were installed autmatically and are no longer needed.

The apt search command

This searches the local APT repository metadata for packages that match the given expression, for example:

  
  

sudo apt search bash

  
  

On my system, this returns the following:

  
  

i   bash                                                                   - GNU Bourne Again SHell (interpréteur de commandes dérivé de celui de S. Bourne)
p   bash:i386                                                              - GNU Bourne Again SHell (interpréteur de commandes dérivé de celui de S. Bourne)
p   bash-argsparse                                                         - High level argument parsing library for bash
p   bash-builtins                                                          - Fonctions incorporables dans Bash : en-têtes et exemples
p   bash-builtins:i386                                                     - Fonctions incorporables dans Bash : en-têtes et exemples
i   bash-completion                                                        - complétions programmables pour l'interpréteur bash
v   bash-completion:i386                                                   -
p   bash-doc                                                               - Documentation et exemples pour BASH (« Bourne Again SHell », interpréteur de scr
p   bash-static                                                            - GNU Bourne Again SHell − version statique
p   bash-static:i386                                                       - GNU Bourne Again SHell − version statique
p   bashtop                                                                - Resource monitor that shows usage and stats
v   dh-sequence-bash-completion                                            -
p   elpa-bash-completion                                                   - add programmable bash completion to Emacs shell-mode
p   libbash                                                                - bibliothèques bash partagées de style dynamique
p   libbash-doc                                                            - bash dynamic-like shared libraries - documentation
p   netdata-plugins-bash                                                   - real-time performance monitoring (bash plugins)
p   node-bash                                                              - Utilities for using bash from node.js
p   node-bash-color                                                        - wrap strings in color codes for pretty printing in bash
p   node-bash-match                                                        - Node module to match strings using bash
p   oem-sutton-bash-meta                                                   - hardware support for Lenovo ThinkStation P5
p   python-bashate-doc                                                     - bash script style guide checker - doc
p   python3-bashate                                                        - contrôleur de guide de style pour script bash –⋅Python 3.x
p   python3-colcon-bash                                                    - collective construction meta build tool - bash extension

  
  

But we can also give RegEx.

  
  

sudo apt search '^bash$'

  
  

Returns the following:

  
  

i   bash                                                                   - GNU Bourne Again SHell (interpréteur de commandes dérivé de celui de S. Bourne)
p   bash:i386                                                              - GNU Bourne Again SHell (interpréteur de commandes dérivé de celui de S. Bourne)

  
  

The second line still refers to the bash package, while the :i386 is just architecture qualifier added to the displayed layout.

The apt show command

This is the command to query informations about an installed package, it's a little bit like looking into the Packages index file containing the package infos but with additional infromations concerning the status of tha package on the system.

For example:

  
  

apt show bash

  
  

Returns:

  
  

Package: bash
Version: 5.2.21-2ubuntu4
Priority: required
Essential: yes
Section: shells
Origin: Ubuntu
Maintainer: Ubuntu Developers <ubuntu-devel-discuss@lists.ubuntu.com>
Original-Maintainer: Matthias Klose <doko@debian.org>
Bugs: https://bugs.launchpad.net/ubuntu/+filebug
Installed-Size: 1 946 kB
Pre-Depends: libc6 (>= 2.38), libtinfo6 (>= 6)
Depends: base-files (>= 2.1.12), debianutils (>= 5.6-0.1)
Recommends: bash-completion
Suggests: bash-doc
Homepage: http://tiswww.case.edu/php/chet/bash/bashtop.html
Task: minimal
Download-Size: 794 kB
APT-Manual-Installed: yes
APT-Sources: http://archive.ubuntu.com/ubuntu noble/main amd64 Packages
Description: ...

  
  

Also, that's a good moment to talk about the 2 lasting fields we have not talked about.

First, the Priority flag.

As its name suggest, it describe how much the package is important to the system.

Typical values are:

  
  

required
important
standard
optional
extra

  
  

Task groups packages into system installation purposes.

For example:

  
  

Task: minimal

  
  

means the package belongs to the minimal task set, in other terms the set of packages intended for a minimal system.

Possible values are:

  
  

minimal
standard
desktop
server
ssh-server
ubuntu-desktop
kubuntu-desktop
xubuntu-desktop
language-related tasks

  
  

A package can belong to one or more tasks.

Priority and Taks are therefore different concepts but there's a strong correlation between them, for example a package belonging to the minimal set has a lot of "chances" of being high priority.

The apt-get familly

apt-get, apt-cache, and apt-mark are older/specialized APT tools.

apt covers most of the operations of those one but not all, that's what we'll see.

apt-file is also specialized, but it is a separate utility rather than just an older frontend superseded by apt.

So I prefere

So, back to apt-get.

We have the equivalent of apt update:

  
  

apt-get update

  
  

But apt-get upgrade is more conservative than apt upgrade.

Indeed, it installs packages when that can be done without installing new packages or removing other installed ones. If a set of packages require a graph change, then they are kept back and those who don't are normally upgraded.

That's why we have the more agressive:

  
  

apt-get dist-upgrade

  
  

Indeed, it upgrades the system while allowing dependency changes, including installing/removing packages when needed. This is the historical apt-get counterpart of apt full-upgrade.

Now, we have a perfect equivalent of apt install pkg with apt-get install pkg.

Also, the perfect equivalent of apt remove pkg with apt-get remove pkg, apt download with apt-get download and apt purge pkg with apt-get purge pkg such as apt autoremove with apt-get autoremove.

But with apt-get, we can even mix the behavior of purge and autoremove with:

  
  

apt-get autopurge

  
  

Or the other form:

  
  

apt-get autoremove --purge

  
  

Here, we apply the un-scoped autoremove operation and the purge operation that is here scoped to the package(s) autoremove did remove contrary to being just scoped to one or few input package(s) with standard apt purge pkg1 pkg2 ....

Now, we also have this command:

  
  

apt-get source pkg

  
  

That won't download the .deb file of the associated package but its source package, in other terms the files that were used to make the corresponding .deb file (and normally also extract the source tree as we'll see later).

It requires to configure a source package repository entry.

Indeed, a .deb entry is like:

  
  

deb-src http://archive.ubuntu.com/ubuntu noble main restricted universe multiverse

  
  

So, a source package entry is like:

  
  

deb-src http://archive.ubuntu.com/ubuntu noble main restricted universe multiverse

  
  

A typical non-native Debian source package using the 3.0 (quilt) format consists of three main files:

  
  

foo_1.2.3-1.dsc
foo_1.2.3.orig.tar.*
foo_1.2.3-1.debian.tar.*

  
  

Their roles are:

  • .dsc -> contains hashes of the source-package files (like the .tar.* files) ans is OpenPGP signed

  • .orig.tar.* -> original upstream source code

  • .debian.tar.* -> Debian/Ubuntu packaging material + maintainer scripts/templates, etc.

Indeed, at first the developper has something like:

  
  

foo-1.2.3/
|-- src/
|   |-- main.c
|   |-- foo.c
|   |-- util.c
|-- include/
|   |-- foo.h
|   |-- util.h
|-- docs/
|-- tests/
|-- Makefile
|-- configure
|-- README
|-- LICENSE
|-- debian/
    |-- control
    |-- rules
    |-- changelog
    |-- patches/
    |   ├── series
    |   └── fix-something.patch
        |-- fix-something2.patch
    |-- foo.install
    |-- foo-doc.install

  
  

The source package is split conceptually into:

  
  

foo_1.2.3.orig.tar.xz

  
  

containing upstream material:

  
  

src/
include/
docs/
tests/
Makefile
configure
README
LICENSE
...

  
  

and:

  
  

foo_1.2.3-1.debian.tar.xz

  
  

containing Debian packaging:

  
  

debian/
|-- control
|-- rules
|-- changelog
|-- patches/
|-- foo.install
|-- ...

  
  

plus:

  
  

foo_1.2.3-1.dsc

  
  

Together, they constitute the Debian source package.

Then the build server unpacks them.

It applies the patch(es) in the order debian/patches/series describe.

Then it invokes the Debian build process, typically through something like:

  
  

dpkg-buildpackage

  
  

Which eventually runs debian/rules.

debian/rules exists because the upstream Makefile and Debian packaging solve different problems.

The upstream Makefile answers:

How do I build this software (being distro agnostic) ?

For example:

  
  

make
make test
make install

  
  

But Debian needs a higher-level recipe that answers:

How do I turn this upstream project into Debian packages ?

That includes things like:

  • how Debian wants to invoke the build

  • which flags Debian wants

  • which staging directories to use

  • which binary packages to produce

  • which debhelper (dh command see later) steps to run

So debian/rules is basically the Debian packaging Makefile / build driver.

For an old-style package it might contain explicit targets like:

  
  

#!/usr/bin/make -f

build:
	./configure --prefix=/usr
	$(MAKE)

install:
	$(MAKE) install DESTDIR=$(CURDIR)/debian/tmp

clean:
	$(MAKE) clean

binary:
	...

  
  

Here debian/rules is calling the project's own:

  • configure

  • Makefile

Technically, we could put Debian-specific packaging logic into the upstream Makefile. But it would be a poor separation of concerns.

The upstream Makefile is supposed to describe how to build the software in a distro-neutral way.

A debian/*.install file tells Debian packaging tools which built/staged files should go into a given binary package, and where they should end up inside that package.

For example:

  
  

# debian/foo.install

usr/bin/foo usr/bin/
usr/bin/foo-helper usr/lib/foo/

  
  

means:

Take debian/tmp/usr/bin/foo and put it in debian/foo/usr/bin/foo/.

Take debian/tmp/usr/bin/foo-helper and put it in debian/foo/usr/lib/foo/.

Indeed, dh_install treats those source paths as coming from the temporary staging area such as debian/tmp, then copies them into the package build directory, which by default is debian/foo/.

So the resulting package staging tree becomes roughly:

  
  

debian/foo/
|-- usr/
|   |-- bin/
|   |   |-- foo
|   |-- lib/
|       |-- foo/
|           |-- foo-helper

  
  

And that is the tree that ultimately becomes the payload of .deb's data.tar.*. It will tell to install the files in the user system at, respectively /usr/bin/fooand /usr/lib/foo.

In the dpkg-buildpackage process, after compiling and before putting the binaries into the stagging location, the upstream build process can end up puting compiled programms into builds for example, so we end up with:

  
  

foo-1.2.3/
├── src/
├── include/
├── debian/
└── build/          <- created by CMake/Meson/etc.
    ├── foo
    ├── foo.o
    └── ...

  
  

This is not the same as debian/tmp/, which is the temporary staging area used by the Debian packaging process (before debian/foo).

We can describe the separations with:

  
  

build/
-> upstream compilation output
-> e.g. build/foo, build/foo.o

debian/tmp/
-> temporary installation staging tree
-> e.g. debian/tmp/usr/bin/foo

debian/foo/
-> final staging tree for binary package foo
-> its contents become data.tar.*

  
  

A last file we have not covered for the package sources is debian/changelog.

It records the packaging history and also supplies important current-build metadata, especially the package version.

Example:

  
  

foo (1.2.3-2) noble; urgency=medium

  * Fix build with GCC 15.
  * Add patch for bug #12345.

 -- Jane Doe <jane@example.com>  Sun, 20 Sep 2026 15:30:00 +0200

  
  

Back to the apt-get familly.

To resolve and instlall depedencies required to build a package from its source package name:

  
  

apt-get build-dep pkg

  
  

or:

  
  

apt build-dep pkg

  
  

For example, if the package declares:

  
  

Build-Depends: gcc, make, libssl-dev, pkg-config

  
  

Then, it will resolve and normally install those dependencies.

If one or more of the required packages conflicts with installed package(s), then they are removed.

We'll see this in more details in this part: [The dpkg-buildpackage command familly](The dpkg-buildpackage command familly)

We also have the apt-get satisfy command.

This is a powerfull command that will try to modify the system and depedencies graph to satisfy the input expression, such as installing a certain version of a package:

  
  

apt-get satisfy 'foo (>= 2.0)'

  
  

Or having a system state where a package is absent:

  
  

apt-get satisfy 'Conflicts: bar'

  
  

We can even do multiple at once:

  
  

apt-get satisfy 'foo (>= 2.0)' 'Conflicts: bar'

  
  

It will prompt you the changes to accept or refuse them.

Or you can just test if the proposed expressions can be satisfied by just simulating the effect:

  
  

apt-get satisfy -s 'foo (>= 2.0)' 'Conflicts: bar'

  
  

or:

  
  

apt-get satisfy --simulate 'foo (>= 2.0)' 'Conflicts: bar'

  
  

If you give contradictory expressions such as foo (>= 2.0) that needs bar but you also say Conflicts: bar, then the command will fail and report why it failed.

So apt-get satisfy is useful not only to make constraints true, but also to test whether a requested dependency state is actually solvable.

To check if you have broken packages and/or depedencies system wide, you can use:

  
  

apt-get check

  
  

or:

  
  

apt check

  
  

The equivalent pacman command is:

  
  

pacman -Dk

  
  

and we can extend those checks using the sync database as we've discussed earlier ith double k:

  
  

pacman -Dkk

  
  

To remove downloaded .deb files from /var/cache/apt/archives and /var/cache/apt/archives/partial, we run:

  
  

apt-get clean

  
  

or:

  
  

apt clean

  
  

Btw, that's a good moment to introduce you to the lockfile concept that we have across a lot (if that's not all) package managers.

Indeed, for APT the file is permant and is at this location:

  
  

/var/cache/apt/archives/lock

  
  

Its state can change between held when an APT process is running and not held when no APT process is running.

Indeed, at the start of an APT commad, the latter will see the state of this lockfile and if it's held, then another APT process is running and to avoid race conditions it'll stop.

We have the same concept for pacman, but differs in meaning.

Here, its location is at:

  
  

/var/lib/pacman/db.lck

  
  

And is only present when a Pacman process is running.

This is also a good moment to introduce the lock-file concept used by package managers to prevent concurrent operations from modifying the same package-management state.

APT uses several lock files for different resources. One of them is:

  
  

/var/cache/apt/archives/lock

  
  

which protects access to the package archive cache.

With APT, the lock file itself can remain permanently on disk. What matters is whether a process currently holds an operating-system-level lock on it.

Before performing an operation requiring that resource, APT attempts to acquire the corresponding lock. If another package-management process already holds it, APT cannot safely perform the conflicting operation until that lock becomes available.

Pacman uses a slightly different mechanism for its package database:

  
  

/var/lib/pacman/db.lck

  
  

When pacman is about to modify its package database, for example during an installation, upgrade, or removal, it creates this file to prevent another pacman transaction from modifying the database simultaneously.

Unlike APT's persistent lock files, pacman's db.lck is normally removed when the transaction finishes.

Therefore:

  
  

APT
lock file can permanently exist
-> the important state is whether the file is currently locked

pacman
db.lck normally exists while a database-modifying transaction is active
-> its presence itself normally indicates that the database is locked

  
  

If pacman is interrupted or crashes, db.lck may remain even though no pacman process is running. This is known as a stale lock file.

Ok, a less agressive command for cleaning the cache is:

  
  

apt-get autoclean

  
  

or:

  
  

apt autoclean

  
  

It removes cached .deb files that can no longer be downloaded from configured repositories, while retaining still-current cached packages.

Now, a very specialized but somewhat usefull command when you want to shrink your image/filesystem before distributing it to ,for example, a container is the one that removes most of the indexes files.

This one:

  
  

apt-get distclean

  
  

removes Translations, Contents and Packages indexes files, it only keep the InRelease ones.

Then, after we have distributed the image, when we'll run apt update so /var/lib/apt/lists will be populated by the required indexes files that have been previously removed.

We have more specialized commands such as:

  
  

apt-get changelog pkg

  
  

Or:

  
  

apt changelog pkg

  
  

That will download and display the package's changelog through a pager.

Therefore, the package doesn't even have to be installed to audit the changelogs.

Now, we have a command that allow to return metadata about the index files APT knows how to fetch:

  
  

apt-get indextargets

  
  

Inded, APT regroup them into class instances and give them different values related to their nature.

They are also assigned a template identifier, for example:

  
  

deb::Contents-deb

  
  

And importantly a filename convention which will be used like a key to retreive files that have the same nature, such as:

  
  

flatMetaKey "Contents-$(ARCHITECTURE)";

  
  

Which correspond to these files for example:

  
  

/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_noble_Contents-amd64.lz4
/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_noble-backports_Contents-i386.lz4
/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_noble_Contents-amd64.lz4
/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_noble_Contents-i386.lz4

  
  

Several special values exists, such as if files must be compressed or not.

We can check inside /etc/apt/apt.conf.d/50apt-file.conf this class for example:

  
  

deb::Contents-deb  {
    MetaKey "$(COMPONENT)/Contents-$(ARCHITECTURE)";
    ShortDescription "Contents-$(ARCHITECTURE)";
    Description "$(RELEASE)/$(COMPONENT) $(ARCHITECTURE) Contents (deb)";

    flatMetaKey "Contents-$(ARCHITECTURE)";
    flatDescription "$(RELEASE) Contents (deb)";
    PDiffs "true";
    KeepCompressed "true";
};

  
  

It describes the files given above (among others).

The PDiffs field means package index diffs: incremental updates for repository index files.

Instead of always downloading a whole new index like:

  
  

Contents-amd64
Packages

  
  

APT can sometimes download only the differences between our old local copy and the new repository version.

Conceptually:

  
  

old Packages + small diff(s) = new Packages

  
  

So with:

  
  

PDiffs "true";

  
  

APT is allowed to use those incremental patch files when the repository provides them.

That can save bandwidth when an index is large but only a small portion changed.

Without PDiffs, the update is more like:

  
  

download entire new Packages.xz
-> replace old local index

  
  

With PDiffs:

  
  

download small incremental patches
-> apply them to old index
-> reconstruct current index

  
  

The apt-cache familly

apt-cache is the read/query side of APT.

It does not install, remove, or upgrade packages.

Indeed, it inspects APT’s local package metadata/cache, which is built from the repository indexes downloaded by apt update. Because it works from local metadata, it can still answer queries even when the repositories are temporarily unreachable

First, we have:

  
  

apt-cache search SOME-REGEX

  
  

It performs a text search over package names and description, so if the RegEx matches the package name and/or the related description then the package name and its summary are returned, for example:

  
  

sudo apt-cache search ssh

  
  

Outputs:

  
  

libpam-fingwit - Smart PAM module for fingerprint authentication
backuppc - système à hautes performances de sauvegarde de PC en entreprise
byobu - gestionnaire de fenêtre en mode texte, multiplexeur de shells, environnement DevOps intégré
dbus-user-session - simple interprocess messaging system (systemd --user integration)
erlang-ssh - mise en œuvre en Erlang/OTP du protocole SSH
gnome-keyring - Services de porte-clés de GNOME (démon et outils)
gnome-keyring-pkcs11 - Module de trousseau de clés GNOME pour la bibliothèque de chargement du module PKCS#11
libpam-gnome-keyring - Module PAM pour déverrouiller le trousseau de GNOME lors de la connexion
libssh-4 - tiny C SSH library (OpenSSL flavor)
libssh-dev - tiny C SSH library - Development files (OpenSSL flavor)
libssh-doc - tiny C SSH library - Documentation files
libssh-gcrypt-4 - tiny C SSH library (gcrypt flavor)
libssh-gcrypt-dev - tiny C SSH library - Development files (gcrypt flavor)
libssh2-1-dev - SSH2 client-side library (development headers)
libssh2-1t64 - Bibliothèque client SSH2
openssh-client - Client shell (SSH), pour accèder de manière sécurisée à des machines distantes
openssh-server - Serveur Secure Shell (SSH), pour un accès sécurisé à partir de machines distantes
paramiko-doc - Effectuer des connexions SSH v2 avec Python (Documentation)

  
  

We can also tell APT to only search for packages names (and not descriptions) with the --names-only flag (this is a convenience flag that temporary modifies the APT conf for the related command, see later with the -o option).

For example:

  
  

apt-cache --names-only search ssh

  
  

Outputs:

  
  

erlang-ssh - mise en œuvre en Erlang/OTP du protocole SSH
libssh-4 - tiny C SSH library (OpenSSL flavor)
libssh-dev - tiny C SSH library - Development files (OpenSSL flavor)
libssh-doc - tiny C SSH library - Documentation files
libssh-gcrypt-4 - tiny C SSH library (gcrypt flavor)
libssh-gcrypt-dev - tiny C SSH library - Development files (gcrypt flavor)
libssh2-1-dev - SSH2 client-side library (development headers)
libssh2-1t64 - Bibliothèque client SSH2
...

  
  

All the returned packages name matche the input RegEx.

Then, we can query the famous information block for a package and its downloaded versions using:

  
  

apt-cache show pkg

  
  

For example:

  
  

apt-cache show ssh

  
  

Outputs:

  
  

Package: ssh
Source: openssh
Priority: optional
Section: net
Installed-Size: 58
Maintainer: Ubuntu Developers <ubuntu-devel-discuss@lists.ubuntu.com>
Architecture: all
Version: 1:9.6p1-3ubuntu13.19
Depends: openssh-client (>= 1:9.6p1-3ubuntu13.19), openssh-server (>= 1:9.6p1-3ubuntu13.19)
Filename: pool/main/o/openssh/ssh_9.6p1-3ubuntu13.19_all.deb
Size: 4658
MD5sum: fd5bc288bd9f1909ca24e0a6e87f3d20
SHA1: 79de0e7d2738694d98f8e50a0428fb9964157615
SHA256: 413a26c1ba964619715d871acbe212c6ac17349409a14a1d61055b9e75e936be
SHA512: 67318f4919e7b8b06e169fe18bf33826528ceec5820c2153a6aca963a8bc396c9f84fa75aca63be50eb689cc195b3c0e4c12e38f03192b5783e0d562935da61a
Homepage: https://www.openssh.com/
Description-fr: client et serveur shell sécurisé –⋅métapaquet
 Ce métapaquet est une manière pratique d'installer à la fois le client et
 le serveur OpenSSH. Il ne fournit rien en lui-même, aussi il peut être
 supprimé si aucun paquet ne dépend de lui.
Description-md5: b00e309365895c14a10af55945efb136
Multi-Arch: foreign
Original-Maintainer: Debian OpenSSH Maintainers <debian-ssh@lists.debian.org>
Origin: Ubuntu
Bugs: https://bugs.launchpad.net/ubuntu/+filebug

Package: ssh
Architecture: all
Version: 1:9.6p1-3ubuntu13
Multi-Arch: foreign
Priority: optional
Section: net
Source: openssh
Origin: Ubuntu
Maintainer: Ubuntu Developers <ubuntu-devel-discuss@lists.ubuntu.com>
Original-Maintainer: Debian OpenSSH Maintainers <debian-ssh@lists.debian.org>
Bugs: https://bugs.launchpad.net/ubuntu/+filebug
Installed-Size: 56
Depends: openssh-client (>= 1:9.6p1-3ubuntu13), openssh-server (>= 1:9.6p1-3ubuntu13)
Filename: pool/main/o/openssh/ssh_9.6p1-3ubuntu13_all.deb
Size: 4650
MD5sum: a28fa73500ea0d2c093c5313831d0e16
SHA1: 55e960cc4f0a1c3ad8977abfce94ead1e1d85e5f
SHA256: dabdba578184f38c1b5525174ee96de35ea810c8e289eae7a4e10e6972858647
SHA512: 141a3783919618ab8e0766e64a63e38df04078e3c1bf5288a55df864e49f53955ba3f52cd0ddaaf27b8c59255448e63db9a872cf0f3474906c3b9d522f3a4bb9
Homepage: https://www.openssh.com/
Description-fr: client et serveur shell sécurisé –⋅métapaquet
 Ce métapaquet est une manière pratique d'installer à la fois le client et
 le serveur OpenSSH. Il ne fournit rien en lui-même, aussi il peut être
 supprimé si aucun paquet ne dépend de lui.
Description-md5: b00e309365895c14a10af55945efb136

  
  

We can limit to just return the info block for the candidate versions with the --no-all-versions flag:

  
  

sudo apt-cache --no-all-versions show ssh

  
  

Outputs:

  
  

Package: ssh
Source: openssh
Priority: optional
Section: net
Installed-Size: 58
Maintainer: Ubuntu Developers <ubuntu-devel-discuss@lists.ubuntu.com>
Architecture: all
Version: 1:9.6p1-3ubuntu13.19
Depends: openssh-client (>= 1:9.6p1-3ubuntu13.19), openssh-server (>= 1:9.6p1-3ubuntu13.19)
Filename: pool/main/o/openssh/ssh_9.6p1-3ubuntu13.19_all.deb
Size: 4658
MD5sum: fd5bc288bd9f1909ca24e0a6e87f3d20
SHA1: 79de0e7d2738694d98f8e50a0428fb9964157615
SHA256: 413a26c1ba964619715d871acbe212c6ac17349409a14a1d61055b9e75e936be
SHA512: 67318f4919e7b8b06e169fe18bf33826528ceec5820c2153a6aca963a8bc396c9f84fa75aca63be50eb689cc195b3c0e4c12e38f03192b5783e0d562935da61a
Homepage: https://www.openssh.com/
Description-fr: client et serveur shell sécurisé –⋅métapaquet
 Ce métapaquet est une manière pratique d'installer à la fois le client et
 le serveur OpenSSH. Il ne fournit rien en lui-même, aussi il peut être
 supprimé si aucun paquet ne dépend de lui.
Description-md5: b00e309365895c14a10af55945efb136
Multi-Arch: foreign
Original-Maintainer: Debian OpenSSH Maintainers <debian-ssh@lists.debian.org>
Origin: Ubuntu
Bugs: https://bugs.launchpad.net/ubuntu/+filebug

N: Il y a 1 enregistrement supplémentaire. Veuillez utiliser l'opérande « -a » pour le voir

  
  

The candidate version is the version APT would currently choose if we ran:

  
  

sudo apt install pkg

  
  

It may differ from the version actually installed.

Now, we can also have a more depedencies graph focused output of the role of a package in the system with:

  
  

apt-cache showpkg pkg

  
  

It outputs information about the package's known versions, direct dependencies, reverse dependencies, packages that provide it, and some of the APT index files from which its package and description metadata were loaded, such as Packages and Translation-* indexes.

  
  

apt-cache showpkg ssh

  
  

Outputs:

  
  

1:9.6p1-3ubuntu13.19 (/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_noble-updates_main_binary-amd64_Packages) (/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_noble-updates_main_binary-i386_Packages) (/var/lib/apt/lists/security.ubuntu.com_ubuntu_dists_noble-security_main_binary-amd64_Packages) (/var/lib/apt/lists/security.ubuntu.com_ubuntu_dists_noble-security_main_binary-i386_Packages)
 Description Language:
                 File: /var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_noble_main_binary-amd64_Packages
                  MD5: b00e309365895c14a10af55945efb136
 Description Language: fr
                 File: /var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_noble_main_i18n_Translation-fr
                  MD5: b00e309365895c14a10af55945efb136
 Description Language: en
                 File: /var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_noble_main_i18n_Translation-en
                  MD5: b00e309365895c14a10af55945efb136
 Description Language:
                 File: /var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_noble-updates_main_binary-amd64_Packages
                  MD5: b00e309365895c14a10af55945efb136

1:9.6p1-3ubuntu13 (/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_noble_main_binary-amd64_Packages) (/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_noble_main_binary-i386_Packages)
 Description Language:
                 File: /var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_noble_main_binary-amd64_Packages
                  MD5: b00e309365895c14a10af55945efb136
 Description Language: fr
                 File: /var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_noble_main_i18n_Translation-fr
                  MD5: b00e309365895c14a10af55945efb136
 Description Language: en
                 File: /var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_noble_main_i18n_Translation-en
                  MD5: b00e309365895c14a10af55945efb136
 Description Language:
                 File: /var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_noble-updates_main_binary-amd64_Packages
                  MD5: b00e309365895c14a10af55945efb136


Reverse Depends:
  ssh-askpass-gnome,ssh 1:3.5p1-3
  openssh-client,ssh
  ssh-askpass-gnome:i386,ssh 1:3.5p1-3
  ubuntu-boot-test,ssh
  ssh-askpass-gnome,ssh 1:3.5p1-3
  ssh-askpass-gnome,ssh 1:1.2pre7-4
  openssh-server:i386,ssh
  openssh-client:i386,ssh
  openssh-server,ssh
  openssh-client,ssh
  ssh-askpass-gnome:i386,ssh 1:3.5p1-3
  xtightvncviewer,ssh
  ubuntu-boot-test,ssh
  ssh-askpass-gnome,ssh 1:1.2pre7-4
  pdsh,ssh 1:3.8.1p1-9
  slbackup-php,ssh
  slbackup,ssh
  runoverssh,ssh
  rancid,ssh
  proxytunnel,ssh
  dirvish,ssh 3.4p1
  mrbayes-mpi,ssh
  mew-beta,ssh
  mew,ssh
  mandos-client,ssh
  maildirsync,ssh
  im,ssh
  hdup,ssh
  dish,ssh
  dupload,ssh
  chake,ssh
  cedar-backup3,ssh
  bootcd,ssh
  openssh-server:i386,ssh
  openssh-client:i386,ssh
  openssh-server,ssh
  openssh-client,ssh
Dependencies:
1:9.6p1-3ubuntu13.19 - openssh-client (2 1:9.6p1-3ubuntu13.19) openssh-server (2 1:9.6p1-3ubuntu13.19)
1:9.6p1-3ubuntu13 - openssh-client (2 1:9.6p1-3ubuntu13) openssh-server (2 1:9.6p1-3ubuntu13)
Provides:
1:9.6p1-3ubuntu13.19 - ssh:i386 (= 1:9.6p1-3ubuntu13.19)
1:9.6p1-3ubuntu13 - ssh:i386 (= 1:9.6p1-3ubuntu13)
Reverse Provides:

  
  

A quick summary between Provides and Reverse-Provides concept:

  • Provides -> virtual-package names that THIS package provides

  • Reverse Provides -> packages that provide THIS package name

A virtual package is a package name that does not necessarily correspond to a real .deb file by itself. Instead, other real packages declare that they provide that capability/name.

Classic example:

  
  

mail-transport-agent

  
  

There may be no actual:

  
  

mail-transport-agent_....deb

  
  

Instead, packages such as:

  
  

postfix
exim4-daemon-light

  
  

can declare:

  
  

Provides: mail-transport-agent

  
  

Then another package can depend on the generic capability:

  
  

Depends: mail-transport-agent

  
  

rather than hard-coding:

  
  

Depends: postfix

  
  

APT can then satisfy that dependency with any suitable provider.

pacman has essentially the same concept through provides.

Now, we have a way to see a package depedency relationships and possible package(s) to satisfy it with:

  
  

apt-cache depends pkg

  
  

That's a bit like:

  
  

apt-get satisfy --simulate pkg

  
  

but the latter retruns the required transaction(s) while the first is just descriptive.

For example:

  
  

apt-cache depends ssh

  
  

Returns:

  
  

ssh
  Dépend: openssh-client
    openssh-client:i386
  Dépend: openssh-server
    openssh-server:i386

  
  

Here, the fields such as Pre-Depends, Recommends, Suggests and Conflicts are not returned because they are empty.

We can filter dependency relationship types with flags such as:

  
  

--no-depends
--no-pre-depends
--no-recommends
--no-suggests
--no-conflicts
--no-breaks
--no-replaces
--no-enhances

  
  

Conflicts is the strongest one. If package A says:

  
  

Conflicts: B

  
  

then A and B are not allowed to be unpacked/installed on the system at the same time.

Installing one generally requires removing the other first. Debian Policy explicitly describes Conflicts as stronger than Breaks.

Debian Breaks is weaker and usually version-scoped. If:

  
  

Breaks: B (<< 2.0)

  
  

that means that this version of A makes old versions of B unusable or unconfigurable.

But a sufficiently new B may coexist perfectly fine:

  
  

A
Breaks: B (<< 2.0)

B 1.5 no
B 2.0 yes
B 3.0 yes

  
  

That’s why Debian prefers Breaks for transitions ... or situations where only older versions are incompatible. It gives the resolver more freedom to upgrade B instead of requiring its complete removal.

So the semantic difference is basically:

  • Conflicts -> these packages fundamentally cannot coexist

  • Breaks -> this package breaks certain versions of another package

That distinction matters a lot during upgrades.

Replaces is different again. It is primarily about file ownership/overwriting.

For example, suppose installed package A owns:

  
  

/usr/bin/foo

  
  

and package B is being updated so that now B should own that same file.

Then B may declare:

  
  

Replaces: A

  
  

Which tells dpkg that B is allowed to overwrite files that previously belonged to A.

Two packages can not simutaneously own the same file.

Therefore, Replaces is not a subset of Conflicts.

We can also see the following together:

  
  

Breaks: A (<< 2.0)
Replaces: A (<< 2.0)

  
  

Meaning that old A cannot remain configured with this B and B is also allowed to take ownership of files previously owned by A.

Or sometimes:

  
  

Conflicts: A
Replaces: A

  
  

For a true mutually exclusive replacement.

Now the Enhances value.

Enhances is closely related to Suggests, but in the opposite direction.

Suppose:

  
  

Package: foo
Suggests: foo-plugin

  
  

That means:

“Users of foo may benefit from installing foo-plugin.”

The arrow is:

  
  

foo
  |-- Suggests → foo-plugin

  
  

With Enhances, the plugin can instead declare:

  
  

Package: foo-plugin
Enhances: foo

  
  

meaning:

“Installing me adds/enhances functionality of foo.”

So:

  
  

foo-plugin
    |-- Enhances -> foo

  
  

They describe roughly the same optional relationship from opposite viewpoints.

From the values, we saw, the only one Pacman has no direct equivalent to is Breaks.

Now, the command used to inspect reverse dependency relationships is:

  
  

apt-cache rdepends pkg

  
  

For example:

  
  

apt-cache rdepends ssh

  
  

may output:

  
  

ssh
Reverse Depends:
  ssh-askpass-gnome
  openssh-client
  ubuntu-boot-test
  ...

  
  

Unlike apt-cache depends, the output does not label every result with a visible relationship type such as Depends, Recommends, or Suggests. However, these relationship types still affect which reverse relationships are selected, and they can be filtered with the --no-* options above.

For example:

  
  

apt-cache rdepends ssh | wc -l

  
  

may return:

  
  

33

  
  

while:

  
  

apt-cache rdepends --no-recommends ssh | wc -l

  
  

may return:

  
  

25

  
  

This shows that some of the reverse relationships included in the default output came from Recommends relationships rather than strict Depends relationships.

One thing I did not mention yet is that both depends and rdepends accept the --recurse flag that will traverse all mentioned depedencies and apply the same command, that is show their direct depedencies and so on until the "leafs" of the graphs.

apt-cache policy pkg shows the installed version, the candidate version selected by APT, all known available versions, their pin priorities, and the repository indexes from which those versions are available.

For example!

  
  

sudo apt-cache policy ssh

  
  

Outputs:

  
  

ssh:
  Installé : (aucun)
  Candidate : 1:9.6p1-3ubuntu13.19
 Table de version :
     1:9.6p1-3ubuntu13.19 500
        500 http://archive.ubuntu.com/ubuntu noble-updates/main amd64 Packages
        500 http://archive.ubuntu.com/ubuntu noble-updates/main i386 Packages
        500 http://security.ubuntu.com/ubuntu noble-security/main amd64 Packages
        500 http://security.ubuntu.com/ubuntu noble-security/main i386 Packages
     1:9.6p1-3ubuntu13 500
        500 http://archive.ubuntu.com/ubuntu noble/main amd64 Packages
        500 http://archive.ubuntu.com/ubuntu noble/main i386 Packages

  
  

Here, we see that each versions have multiple providers.

We also see that both versions have the same 500 priority, so APT will still prefere the newer versions that is 1:9.6p1-3ubuntu13.19 500. That's why we see it in Candidate.

The very confusing field is:

  
  

Installé : (aucun)

  
  

In english meaning:

  
  

Installed : (none)

  
  

The missing pience is that there are two different packages:

  
  

ssh
openssh-client

  
  

The package ssh exists in the repositories, so:

  
  

apt-cache policy ssh

  
  

can correctly report:

  
  

Candidate: 1:9.6p1-3ubuntu13.19
Version table:
...

  
  

because APT finds a package record literally named ssh in the Packages indexes files.

But that package is not installed locally, hence:

  
  

Installed: (none)

  
  

We also have:

  
  

apt-cache madison pkg

  
  

that gives a compact table of repositories that provides it, for example:

  
  

apt-cache madison pkg

  
  

Gives:

  
  

ssh | 1:9.6p1-3ubuntu13.19 | http://archive.ubuntu.com/ubuntu noble-updates/main amd64 Packages
ssh | 1:9.6p1-3ubuntu13.19 | http://archive.ubuntu.com/ubuntu noble-updates/main i386 Packages
ssh | 1:9.6p1-3ubuntu13.19 | http://security.ubuntu.com/ubuntu noble-security/main amd64 Packages
ssh | 1:9.6p1-3ubuntu13.19 | http://security.ubuntu.com/ubuntu noble-security/main i386 Packages
ssh | 1:9.6p1-3ubuntu13 | http://archive.ubuntu.com/ubuntu noble/main amd64 Packages
ssh | 1:9.6p1-3ubuntu13 | http://archive.ubuntu.com/ubuntu noble/main i386 Packages

  
  

There is no way to filter an explicit version neither a provider by giving a certain pattern/format. For that we'll use grep orawk.

We also have:

  
  

apt-cache pkgnames [prefix]

  
  

That prints package names APT knows about, optionally restricted by prefix (does not support RegEx). This includes names that may be virtual.

For example here I list packages whose prefix begins with "cup":

  
  

apt-cache pkgnames cup

  
  

This returns:

  
  

cups-filters
cups-backend-bjnp
cup
cups-bsd
cups-common
cupt-dbg
cups-client
cups-ppdc
cups-daemon
cups-x2go
cups-browsed-tests
cups-filters-core-drivers
cups-ipp-utils
cups-tea4cups
cupp3
cups-browsed
cups-pk-helper
cups-core-drivers
cupp
cups
cupt
cups-server-common

  
  

We also can see a bunch of statistics of APT with:

  
  

apt-cache stats

  
  

This returns:

  
  

Total package names: 165741 (4,641 k)
Total package structures: 159560 (7,021 k)
  Normal packages: 88270
  Pure virtual packages: 3329
  Single virtual packages: 51537
  Mixed virtual packages: 3718
  Missing: 12706
Total distinct versions: 110211 (9,699 k)
Total distinct descriptions: 243853 (5,852 k)
Total dependencies: 709203/181127 (17.1 M)
Total ver/file relations: 46320 (1,112 k)
Total desc/file relations: 47245 (1,134 k)
Total Provides mappings: 85160 (2,044 k)
Total globbed strings: 290123 (7,404 k)
Total dependency version space: 100 k
Total slack space: 100 k
Total space accounted for: 57.7 M
Total buckets in PkgHashTable: 196613
  Unused: 91212
  Used: 105401
  Utilization: 53.6084%
  Average entries: 1.51384
  Longest: 20
  Shortest: 1
Total buckets in GrpHashTable: 196613
  Unused: 84516
  Used: 112097
  Utilization: 57.014%
  Average entries: 1.47855
  Longest: 7
  Shortest: 1

  
  

Total package names: is the number of package names APT knows about in its cache. This is not the number of installed packages. It can include real packages, virtual package names...

Remainder, here we reference the APT's cache that is not a throw away memory but rather the semi-permanant files under:

  
  

/var/lib/apt/lists

  
  

This can be viewed as a "cache" from the repositories servers perspective.

Btw, wee see their sizes in bytes inside the related parenthesis, for this one it occupies 4 641 k.

The 4,641 k is associated with the APT cache structures for those package-name entries (structures, pointers, strings...), not with the sizes of the actual .deb packages.

Total package structures: is an internal APT count of package objects/structures allocated in the cache. It's an APT internal.

Now, Normal packages: means ordinary real package names with a straightforward package-name relationship. These are the normal packages we think of, such as bash, coreutils, openssh-client, etc.

Pure virtual packages: means names that exist only as virtual capabilities. There is no real package with that exact name.

Single virtual packages means virtual names that have exactly one provider. So conceptually:

  
  

virtual-name
    ^
    |
provided by exactly one real package

  
  

APT still treats the name as virtual, but there is no ambiguity about which real package provides it.

Mixed virtual packages: is an interesting case: a name exists as an actual package and is also provided by another package.

Conceptually:

  
  

...
Package: foo
...

  
  

exists as a real package, but another package can also say:

  
  

...
Provides: foo
...

  
  

So foo is both a real package name and a provided/virtual name.

Missing: means APT has encountered package names in dependency-like relationships but no package currently provides them.

For example, some metadata might contain:

  
  

Breaks: old-foo

  
  

but no repository we currently have configured contains or provides old-foo.

Then, Total distinct versions: counts package-version objects known to APT.

For example, one package name could have:

  
  

foo 1.0
foo 1.1
foo 2.0

  
  

That is 1 package name and 3 distinct versions.

Because we have several suites such as noble, noble-updates, noble-security, etc., the same package may contribute multiple known versions.

Total distinct descriptions: counts distinct package description records. This can be much larger than the package count because APT can have descriptions for multiple versions and languages as we saw earlier with the Translation indexes files.

Total dependencies: 709203/181127 is about dependency relationship objects in APT's cache.

This includes relationships such as:

  
  

Depends
Pre-Depends
Recommends
Suggests
Conflicts
Breaks
Replaces
Enhances

  
  

The first number represents the dependency records; the second is related to APT's internal dependency grouping/unique structures. This is one of those fields where the exact internal distinction is more implementation-oriented than user-facing.

Total ver/file relations: means mappings between package records and Packages index files.

That is exactly the sort of relationship we saw with apt-cache showpkg.

Similarly Total desc/file relations: counts mappings between description records and index files, such as:

  
  

French bash description
    |
    V comes from
..._Translation-fr

  
  

Then Total Provides mapping: is the count of the Provides relationships (that often gives virtual packages).

Total dependency version space: is memory reserved for the version constraints attached to dependency relationships.

So this value is specifically the space used for the version-expression side of dependencies, not for all dependency objects themselves. The main dependency structures are already counted separately under:

  
  

Total dependencies: ...

  
  

Total globbed strings: is one of the more implementation-specific APT statistics.

It refers to strings that APT stores in a shared/deduplicated form inside its package cache. The idea is that many package records repeat the same text fragments—package names, versions, architecture names, dependency target names, and similar strings; so APT can store one copy and have many structures refer to it instead of duplicating the same bytes over and over.

Total slack space: is unused space inside the cache allocation/layout. APT’s binary cache is organized into allocated regions/structures, and not every allocated byte ends up containing useful data.

And Total space accounted for: is essentially the grand total of the cache space that apt-cache stats has accounted for across all those categories.

Finally the hash-table sections:

  
  

PkgHashTable
GrpHashTable

  
  

are APT's internal hash tables used for fast package/group lookups.

For example:

  
  

Total buckets: 196613
Unused: 91212
Used: 105401
Utilization: 53.6%
Average entries: 1.51
Longest: 20
Shortest: 1

  
  

means APT allocated 196,613 hash buckets, about 53.6% contain entries, and occupied buckets contain about 1.51 entries on average.

Longest: 20 means the longest collision chain/bucket contains 20 entries.

We also have:

  
  

apt-cache unmet

  
  

This walks through APT's current package cache and reports dependency relationships that cannot currently be satisfied by the providers known to APT.

For example, if a package version declares a dependency such as:

  
  

Depends: foo (>= 2.0)

  
  

and APT cannot find a suitable version or provider satisfying that relationship, apt-cache unmet can report the package and the unmet dependency.

We also have the -i flag (or --important), which restricts the output to Pre-Depends and Depends relationships only.

For example:

  
  

apt-cache unmet | wc -l

  
  

may return:

  
  

16670

  
  

While:

  
  

apt-cache unmet --important | wc -l

  
  

May return:

  
  

4601

  
  

Now, let's discuss:

  
  

apt-cache dump

  
  

And:

  
  

apt-cache dumpavail

  
  

Both commands read from APT's current package cache, which is built from the local metadata downloaded from the configured repositories.

Their difference is mainly in how that information is presented.

apt-cache dump prints a short, debugging-oriented representation of every package in the cache. In particular, it makes relationships between package versions, dependency records, descriptions, and the index files from which they originated explicit.

For example:

  
  

apt-cache dump | head

  
  

Output:

  
  

Using Versioning System: Standard .deb
Package: gobjc++-11-multilib-mipsel-linux-gnu
 Version: 11.5.0-1ubuntu1~24.04cross1
     File: /var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_noble-updates_universe_binary-amd64_Packages
  Depends: gcc-11-mipsel-linux-gnu-base 11.5.0-1ubuntu1~24.04cross1
  Depends: gobjc++-11-mipsel-linux-gnu 11.5.0-1ubuntu1~24.04cross1
  Depends: g++-11-multilib-mipsel-linux-gnu 11.5.0-1ubuntu1~24.04cross1
  Depends: gobjc-11-multilib-mipsel-linux-gnu 11.5.0-1ubuntu1~24.04cross1
 Description Language:
                 File: /var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_noble_universe_binary-amd64_Packages

  
  

While:

  
  

apt-cache dumpavail | head

  
  

Output:

  
  

Package: brave-browser
Priority: optional
Section: web
Installed-Size: 473603
Maintainer: Brave Software <support@brave.com>
Architecture: amd64
Version: 1.95.104
Provides: www-browser
Depends: brave-keyring, ca-certificates, fonts-liberation, libasound2 (>= 1.0.17), libatk-bridge2.0-0 (>= 2.5.3), libatk1.0-0 (>= 2.11.90), libatspi2.0-0 (>= 2.9.90), libc6 (>= 2.25), libcairo2 (>= 1.14.0), libcups2 (>= 1.7.0), libcurl3-gnutls | libcurl3-nss | libcurl4 | libcurl3, libdbus-1-3 (>= 1.9.14), libexpat1 (>= 2.1~beta3), libgbm1 (>= 17.1.0~rc2), libglib2.0-0 (>= 2.39.4), libgtk-3-0 (>= 3.9.10) | libgtk-4-1, libnspr4 (>= 2:4.9-2~), libnss3 (>= 2:3.35), libpango-1.0-0 (>= 1.14.0), libudev1 (>= 183), libvulkan1, libx11-6 (>= 2:1.4.99.1), libxcb1 (>= 1.9.2), libxcomposite1 (>= 1:0.4.4-1), libxdamage1 (>= 1:1.1), libxext6, libxfixes3, libxkbcommon0 (>= 0.5.0), libxrandr2, wget, xdg-utils (>= 1.0.2)
Pre-Depends: dpkg (>= 1.14.0)

  
  

Also, this is the time to introduce you to:

  
  

/var/lib/apt/pkgcache.bin
/var/lib/apt/srcpkgcache.bin

  
  

They are the binaries that efficiently store the packages's metadata and package's relations.

The key distinction is:

  
  

srcpkgcache.bin
-> parsed repository-side package metadata
-> built from the package/release files referenced by your APT sources
-> does NOT include /var/lib/dpkg/status

pkgcache.bin
-> fuller package cache used by normal APT operations
-> includes the repository information
-> plus local installed-package state from /var/lib/dpkg/status

  
  

The separation exists mainly for performance and reuse.

Indeed, APT has two kinds of information that change at very different rates:

  
  

repository metadata
-> changes when we run apt update

  
  

And:

  
  

installed-system state
-> changes whenever dpkg/apt installs, removes, configures, etc.

  
  

APT therefore keeps a parsed cache of the relatively stable repository side:

  
  

srcpkgcache.bin

  
  

and can then combine that with:

  
  

/var/lib/dpkg/status

  
  

to build:

  
  

pkgcache.bin

  
  

And where do they get the data ?

From the indexes files.

And that's here that I introduce you to:

  
  

apt-cache gencaches

  
  

That explicitely generates those binaries from the indexes files.

Those files are used by many APT operations to optimize queries.

We can think the build process as:

  
  

/var/lib/apt/lists/*Packages
/var/lib/apt/lists/*Release / InRelease
        |
        V
srcpkgcache.bin
        |
        | + /var/lib/dpkg/status
        V
pkgcache.bin

  
  

And yes /var/lib/dpkg/status contains standard metadata block about installed packages only.

Normally we don’t need to run this because commands that require the cache create/regenerate it automatically when needed.

Now, to get the informations about the source files that were used to build the input package (and potentially others), we use:

  
  

apt-cache showsrc pkg

  
  

Or:

  
  

apt showsrc pkg

  
  

That will work only if we have set up a deb-src provider suite in the /etc/apt/sources.list file on in one file under /etc/apt/sources.list.d/.

For example:

  
  

deb-src http://archive.ubuntu.com/ubuntu noble main restricted universe multiverse

  
  

Then we run:

  
  

apt update

  
  

to download the /var/lib/apt/lists/*source_Sources* indexes files.

On my system, I can show them:

  
  

ls /var/lib/apt/lists/*source_Sources*

  
  

Which outputs:

  
  

/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_noble_main_source_Sources
/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_noble_multiverse_source_Sources
/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_noble_restricted_source_Sources
/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_noble_universe_source_Sources

  
  

Now, the apt-cache pkg command should work, for example:

  
  

apt-cache showsrc ssh

  
  

Which outputs:

  
  

Package: openssh
Format: 3.0 (quilt)
Binary: openssh-client, openssh-server, openssh-sftp-server, openssh-tests, ssh, ssh-askpass-gnome, openssh-client-udeb, openssh-server-udeb
Architecture: any all
Version: 1:9.6p1-3ubuntu13
Priority: standard
Section: net
Maintainer: Ubuntu Developers <ubuntu-devel-discuss@lists.ubuntu.com>
Original-Maintainer: Debian OpenSSH Maintainers <debian-ssh@lists.debian.org>
Uploaders: Colin Watson <cjwatson@debian.org>, Matthew Vernon <matthew@debian.org>,
Standards-Version: 4.6.2
Build-Depends: debhelper (>= 13.1~), debhelper-compat (= 13), dh-exec, dh-runit (>= 2.8.8), dh-sequence-movetousr, libaudit-dev [linux-any], libedit-dev, libfido2-dev (>= 1.5.0) [linux-any], libgtk-3-dev <!pkg.openssh.nognome>, libkrb5-dev | heimdal-dev, libpam0g-dev | libpam-dev, libselinux1-dev [linux-any], libssl-dev (>= 1.1.1), libwrap0-dev | libwrap-dev, pkg-config, zlib1g-dev, systemd-dev
Testsuite: autopkgtest
Testsuite-Triggers: devscripts, dropbear, haveged, krb5-admin-server, krb5-kdc, openssl, putty-tools, python3-twisted, sudo, systemd, sysvinit-utils
Homepage: https://www.openssh.com/
Vcs-Browser: https://salsa.debian.org/ssh-team/openssh
Vcs-Git: https://salsa.debian.org/ssh-team/openssh.git
Directory: pool/main/o/openssh
Package-List:
 openssh-client deb net standard arch=any
 openssh-client-udeb udeb debian-installer optional arch=any profile=!noudeb
 openssh-server deb net optional arch=any
 openssh-server-udeb udeb debian-installer optional arch=any profile=!noudeb
 openssh-sftp-server deb net optional arch=any
 openssh-tests deb net optional arch=any
 ssh deb net optional arch=all
 ssh-askpass-gnome deb gnome optional arch=any profile=!pkg.openssh.nognome
Files:
 af048ba3e30ff18118768df92040e3d7 3334 openssh_9.6p1-3ubuntu13.dsc
 5e90def5af3ffb27e149ca6fff12bef3 1857862 openssh_9.6p1.orig.tar.gz
 a9aaf09b36b23327431072ed804d7094 833 openssh_9.6p1.orig.tar.gz.asc
 8655501e95e2b68e023be73776694686 203240 openssh_9.6p1-3ubuntu13.debian.tar.xz
Checksums-Sha1:
 391755e1b3f8d95ec763513f7732b29fba5c4ead 3334 openssh_9.6p1-3ubuntu13.dsc
 de300d09ec79fdbf37de4e6672cce4161439f2c3 1857862 openssh_9.6p1.orig.tar.gz
 63c241035c665da9284965575cd96e0467bf09c1 833 openssh_9.6p1.orig.tar.gz.asc
 a1e8d5723b838b729532f5702f7f5a578a04a746 203240 openssh_9.6p1-3ubuntu13.debian.tar.xz
Checksums-Sha256:
 046369010a2c8bd26252292dd487285c93ad0bf75b712c1e95ce9695eed17d85 3334 openssh_9.6p1-3ubuntu13.dsc
 910211c07255a8c5ad654391b40ee59800710dd8119dd5362de09385aa7a777c 1857862 openssh_9.6p1.orig.tar.gz
 9b1e931cbc811f02e91f7eacd55f8211cc45dade11975462f4b0dcdad29927aa 833 openssh_9.6p1.orig.tar.gz.asc
 2e7736d76ac31b98e2be99b833b7280f65e4a6af3cbeef25e9e836dd93792385 203240 openssh_9.6p1-3ubuntu13.debian.tar.xz
Checksums-Sha512:
 0a91899087eafeca575d76d90b8b3ad6c05434886b5956398f9d8bb4d0e4a9cddd288572fe2ce2bdbde759dfd8ba41fb92095228ebf0c03a533189125116015a 3334 openssh_9.6p1-3ubuntu13.dsc
 0ebf81e39914c3a90d7777a001ec7376a94b37e6024baf3e972c58f0982b7ddef942315f5e01d56c00ff95603b4a20ee561ab918ecc55511df007ac138160509 1857862 openssh_9.6p1.orig.tar.gz
 aec5a5bd6ce480a8e5b5879dc55f8186aec90fe61f085aa92ad7d07f324574aa781be09c83b7443a32848d091fd44fb12c1842d49cee77afc351e550ffcc096d 833 openssh_9.6p1.orig.tar.gz.asc
 1b25983f4bee107363cc201f9289efd57b4bf1f28be78cb985647c9187bba37cf638f2d008a60976e89457d9865f70d97315a7be2cb0ca32b4f66c651d0755c4 203240 openssh_9.6p1-3ubuntu13.debian.tar.xz

  
  

Indeed, it made the relations by looking in the Binary field where we see that ssh appears.

Now, we also have the equivalent to:

  
  

pactree -g pkg 

  
  

with

  
  

apt-cache dotty pkg

  
  

That will output a DOT structured output for the dot utility to convert it to an SVG, PNG, JPEG, JPG, PDF etcetera...

So, we can do the following:

  
  

apt-cache dotty brave-browser > brave.dot

  
  

And then:

  
  

dot -Tpdf brave.dot -o brave-graph.pdf

  
  

But often, when the depedencies graph is huge, this will fail (core dumped).

Indeed, for example look at the number of depedencies of brave-browser:

  
  

apt-cache dotty brave-browser | wc -l

  
  

Returns:

  
  

8677

  
  

In order to reduce this graph-size problem, we can temporarily override one of APT's configuration options.

APT exposes many configuration keys that its commands read at runtime to determine their behavior. They generally follow a hierarchical naming scheme such as:

  
  

APT::Cache::Field

  
  

and can be overridden for a single command with:

  
  

-o Key=Value

  
  

For apt-cache dotty, one useful option is:

  
  

APT::Cache::GivenOnly=true|false

  
  

By default, dotty recursively follows dependency relationships starting from the packages given on the command line.

When:

  
  

APT::Cache::GivenOnly=true

  
  

APT restricts the graph to the packages explicitly supplied as arguments instead of recursively expanding the dependency tree.

We can therefore use:

  
  

apt-cache -o APT::Cache::GivenOnly=true dotty brave-browser | wc -l

  
  

which may return a much smaller result, for example:

  
  

77

  
  

We can then generate a much smaller DOT file:

  
  

apt-cache -o APT::Cache::GivenOnly=true dotty brave-browser > brave.dot

  
  

This temporary override applies only to that invocation of apt-cache; it does not permanently modify APT's configuration files.

Anyway, here's a PDF dotty coupled with dot can produce:

less-graph.pdf

The apt-file command familly

After apt-cache, which queries package metadata, apt-file lets us query the file contents advertised by repository packages. Instead of asking “what does this package depend on?”, we can now ask “which package contains this file?” or “which files would this package install/own ?” thanks to the Contents indexes files.

In order answer to the first question, we do:

  
  

apt-file search /path/to/some/file

  
  

Or:

  
  

apt-file find /path/to/some/file

  
  

Indeed, a synonim of search is find.

For example, if we search who owns /usr/bin/bash, we do:

  
  

sudo apt-file search -x ^/usr/bin/bash$

  
  

or:

  
  

sudo apt-file search --regexp ^/usr/bin/bash$

  
  

Note that we used a regular expression to explicitly just match the given file /usr/bin/bash which returns:

  
  

bash: /usr/bin/bash

  
  

Instead of this:

  
  

sudo apt-file search /usr/bin/bash

  
  

Which would return something like:

  
  

bash: /usr/bin/bash
bash: /usr/bin/bashbug
bash-static: /usr/bin/bash-static
bashtop: /usr/bin/bashtop
python3-bashate: /usr/bin/bashate

  
  

You also can filter to only keep the package names with the -l flag, so:

  
  

sudo apt-file search -l /usr/bin/bash

  
  

Would return something like:

  
  

bash
bash-static
bashtop
python3-bashate

  
  

We can also answer the second question with:

  
  

apt-file show pkg

  
  

or:

  
  

apt-file list pkg

  
  

Indeed, here show and list are synonims.

And at the opposite of the search/find sub-commands, the default behavior of show/list is to take the input package name as a "strict matching string".

That's why:

  
  

sudo apt-file show bash

  
  

Only returns the files owned by the package, and not all the files owned by packages which the name contains the "bash" substring.

Here its output:

  
  

bash: /etc/bash.bashrc
bash: /etc/skel/.bash_logout
bash: /etc/skel/.bashrc
bash: /etc/skel/.profile
bash: /usr/bin/bash
bash: /usr/bin/bashbug
bash: /usr/bin/clear_console
bash: /usr/bin/rbash
bash: /usr/share/debianutils/shells.d/bash
bash: /usr/share/doc/bash/COMPAT.gz
bash: /usr/share/doc/bash/INTRO.gz
bash: /usr/share/doc/bash/NEWS.gz
bash: /usr/share/doc/bash/POSIX.gz
bash: /usr/share/doc/bash/RBASH
bash: /usr/share/doc/bash/README.Debian.gz
bash: /usr/share/doc/bash/README.abs-guide
bash: /usr/share/doc/bash/README.commands.gz
bash: /usr/share/doc/bash/README.gz
bash: /usr/share/doc/bash/changelog.Debian.gz
bash: /usr/share/doc/bash/copyright
bash: /usr/share/doc/bash/inputrc.arrows
bash: /usr/share/lintian/overrides/bash
bash: /usr/share/man/man1/bash.1.gz
bash: /usr/share/man/man1/bashbug.1.gz
bash: /usr/share/man/man1/clear_console.1.gz
bash: /usr/share/man/man1/rbash.1.gz
bash: /usr/share/man/man7/bash-builtins.7.gz
bash: /usr/share/menu/bash

  
  

Same as:

  
  

apt-file list -x ^bash$

  
  

Therefore, to get all the files owned by packages whose names contains a substring equal to the input value, we just do:

  
  

apt-file list -x pkg

  
  

Which returns a much larger output:

For example:

  
  

apt-file list -x bash | wc -l

  
  

Returns:

  
  

1407

  
  

Or:

  
  

apt-file show -x bash | sed -n '25,45p'

  
  

Returns:

  
  

bash: /usr/share/man/man1/clear_console.1.gz
bash: /usr/share/man/man1/rbash.1.gz
bash: /usr/share/man/man7/bash-builtins.7.gz
bash: /usr/share/menu/bash
bash-argsparse: /usr/share/bash-argsparse/argsparse-completion.sh
bash-argsparse: /usr/share/bash-argsparse/argsparse.sh
bash-argsparse: /usr/share/doc/bash-argsparse/README.md
bash-argsparse: /usr/share/doc/bash-argsparse/changelog.Debian.gz
bash-argsparse: /usr/share/doc/bash-argsparse/copyright
bash-argsparse: /usr/share/doc/bash-argsparse/html/argsparse-completion_8sh.html
bash-argsparse: /usr/share/doc/bash-argsparse/html/argsparse_8sh.html
bash-argsparse: /usr/share/doc/bash-argsparse/html/bc_s.png
bash-argsparse: /usr/share/doc/bash-argsparse/html/bc_sd.png
bash-argsparse: /usr/share/doc/bash-argsparse/html/closed.png
bash-argsparse: /usr/share/doc/bash-argsparse/html/doc.svg
bash-argsparse: /usr/share/doc/bash-argsparse/html/docd.svg
bash-argsparse: /usr/share/doc/bash-argsparse/html/doxygen.css
bash-argsparse: /usr/share/doc/bash-argsparse/html/doxygen.svg
bash-argsparse: /usr/share/doc/bash-argsparse/html/dynsections.js
bash-argsparse: /usr/share/doc/bash-argsparse/html/files.html
bash-argsparse: /usr/share/doc/bash-argsparse/html/folderclosed.svg

  
  

We also have a way to see wichi types of Contents indexes files are set up so apt-file can search in:

  
  

apt-file list-indices 

  
  

On my system, it returns:

  
  

+-----------------+-----------------------------+-----------------+
| Index Name (-I) | DefaultEnabled (Apt config) | Index Status    |
+-----------------+-----------------------------+-----------------+
| deb             | <unset>                     | Ok              |
| udeb            | false                       | Empty (code: 4) |
| dsc             | false                       | Empty (code: 4) |
+-----------------+-----------------------------+-----------------+

  
  

Meaning that APT knows about the deb, udeb and the dsc (source packages), but only the normal deb Contents are downloaded.

The usage of the deb Contents files is unset, but from what we already did it appears that their usable by apt-file.

But the usage of the others Contents types by apt-file is explicitly disabled.

Btw, it's a good time to introduce you to the -I flag for apt-file sub-command.

Indeed, that's the flag that will temporary set the Contents indexes files types apt-file is allowed to search in.

For example, passing it the deb value won't change anything in my current setup because that's already the only available type:

  
  

apt-file -I deb show -x ^bash$ | wc -l

  
  

Returns:

  
  

28

  
  

But look at what happen if I set it to udeb type which isn't set:

  
  

apt-file -I udeb show -x ^bash$

  
  

It returns a cache error:

  
  

Finding relevant cache files to search ...E: No Contents in the cache with the given restrictions

  
  

The apt-mark command familly

apt-mark is the APT utility used to inspect and modify package state flags rather than to install, remove, or query repository metadata directly.

Its main role is to record why an installed binary package is present on the system; either because it was explicitly requested (manual) or because it was installed as a dependency (auto) and to manage dpkg selection states such as hold, install, remove, and purge.

So first, we can see the packages whose install reason is manual or auto with respectively:

  
  

apt-mark showmanual

  
  

And:

  
  

apt-mark showauto

  
  

We can also directly test if the install reason of a package is manual or auto with:

  
  

apt-mark showmanual pkg

  
  

If the command returns the package name passed (pkg), then its install reason is manual, if no, then it returns nothing.

That's a bit like:

  
  

pacman -T expression

  
  

in that sense.

For example:

  
  

apt-mark showmanual openssh-client

  
  

Returns:

  
  

openssh-client

  
  

Of course, we have the auto equivalent:

  
  

apt-mark showauto auto

  
  

Now, the equivalent of:

  
  

pacman -D --asexplicit pkg

  
  

and:

  
  

pacman -D --asdeps pkg

  
  

are respectively:

  
  

apt-mark manual pkg

  
  

and:

  
  

apt-mark auto pkg

  
  

Packages whose install reason is auto are written inside:

  
  

/var/lib/apt/extended_states

  
  

as blocks such as:

  
  

Package: libsasl2-modules-gssapi-mit
Architecture: amd64
Auto-Installed: 1

Package: libzxing3
Architecture: amd64
Auto-Installed: 1

...

  
  

We don't need to manually update this file when we change the install reason of a package because apt-mark automatically handles it.

We also have the hold concept on a package.

It tells APT to keep the package(s) at its current state/version and prevent normal automatic installation, upgrade, or removal operations from changing it.

Example:

  
  

apt-mark hold openssh-client

  
  

And to remove its hold state, we simply do:

  
  

apt-mark unhold openssh-client

  
  

We can also list the packages that have the hold state with:

  
  

apt-mark showhold

  
  

Now, this is a good time to introduce you to the apt-get dselect-upgrade command, yess I know that's an apt-get command that I did not mention in the appropriate part but it would be better understood in combinations with:

  
  

apt-mark install pkg

  
  

That will set the install state to pkg (installable from configured repos). So we don't install pkg but just tell APT that it would have to install pkg later if we perform the command that will reconciliate the current system and the desired states.

This operation is done with:

  
  

apt-get dselect-upgrade

  
  

And it's not just about installing packages, but also removing packages with:

  
  

apt-mark remove pkg

  
  

And also purging them with:

  
  

apt-mark purge pkg

  
  

We can inspect those selections with:

  
  

apt-mark showinstall
apt-mark showremove
apt-mark showpurge

  
  

Note that hold, install, remove and purge are exclusive states (think of "disjoints" sets), meaning that a package can not belong to more than one state at the same time.

Now, we have this command:

  
  

apt-cache minimize-manual

  
  

It's usefull to run after the installation process, where a lot of packages that later becomes depedencies for other packages were manually installed.

Indeed, because at first it can look normal:

  
  

pkgA # manual
pkgB # manual
pkgC # manual

  
  

But maybe those packages are depedencies to future installed pkgD, then when pkgD is installed it may be better to change certain graph metadata.

In this case see which packages install reason is manual but are effectively only depedencies to other package(s).

The apt-config command familly

This is the command relative to the APT configuration.

We can for example dump all configuration variables and their associated value(s) with:

  
  

apt-config dump

  
  

This will search them into files under:

  
  

/etc/apt/apt.conf.d

  
  

There are A LOT of rows:

  
  

apt-config dump | wc -l

  
  

Returns something like:

  
  

341

  
  

Here's the result's head:

  
  

apt-config dump | head -n 30

  
  

Output:

  
  

APT "";
APT::Architecture "amd64";
APT::Build-Essential "";
APT::Build-Essential:: "build-essential";
APT::Install-Recommends "1";
APT::Install-Suggests "0";
APT::Key "";
APT::Key::Assert-Pubkey-Algo ">=rsa1024,ed25519,ed448,nistp256,nistp384,nistp512,brainpoolP256r1,brainpoolP320r1,brainpoolP384r1,brainpoolP512r1,secp256k1";
APT::Key::Assert-Pubkey-Algo::Next ">=rsa2048,ed25519,ed448,nistp256,nistp384,nistp512";
APT::Key::Assert-Pubkey-Algo::Future ">=rsa3072,ed25519,ed448";
APT::Sandbox "";
APT::Sandbox::User "_apt";
APT::Authentication "";
APT::Authentication::TrustCDROM "true";
APT::NeverAutoRemove "";
APT::NeverAutoRemove:: "^firmware-linux.*";
APT::NeverAutoRemove:: "^linux-firmware$";
APT::NeverAutoRemove:: "^linux-image-[a-z0-9]*$";
APT::NeverAutoRemove:: "^linux-image-[a-z0-9]*-[a-z0-9]*$";
APT::NeverAutoRemove:: "^postgresql.*-16";
APT::VersionedKernelPackages "";
APT::VersionedKernelPackages:: "linux-.*";
APT::VersionedKernelPackages:: "kfreebsd-.*";
APT::VersionedKernelPackages:: "gnumach-.*";
APT::VersionedKernelPackages:: ".*-modules";
APT::VersionedKernelPackages:: ".*-kernel";
APT::Never-MarkAuto-Sections "";
APT::Never-MarkAuto-Sections:: "metapackages";
APT::Never-MarkAuto-Sections:: "tasks";
APT::Move-Autobit-Sections "";

  
  

See ?

Some variable are in fact lists/arrays containing multiple values, that's why we have the following for example:

  
  

APT::NeverAutoRemove "";
APT::NeverAutoRemove:: "^firmware-linux.*";
APT::NeverAutoRemove:: "^linux-firmware$";
APT::NeverAutoRemove:: "^linux-image-[a-z0-9]*$";
APT::NeverAutoRemove:: "^linux-image-[a-z0-9]*-[a-z0-9]*$";
APT::NeverAutoRemove:: "^postgresql.*-16";

  
  

If we know the variable name, we can directly query its value and set it to a personal shell value, for example:

  
  

apt-config shell FOO APT::Install-Recommends

  
  

Returns:

  
  

FOO='1'

  
  

And in shell scripting, I can evaluate that expression to make "FOO" a usable variables:

  
  

eval $(apt-config shell FOO APT::Install-Recommends)
echo $FOO

  
  

Returns 1.

The dpkg command familly

APT handles repositories, dependency resolution, downloads, candidate selection, upgrade planning, etc. dpkg handles the local .deb transaction itself: unpacking files, configuring packages, running maintainer scripts, removing packages, and maintaining the local package database.

The most important dpkg commands are these.

  
  

dpkg -i package.deb
dpkg --install package.deb

  
  

This installs a local .deb. The operation is more than just extracting files: dpkg extracts control information, runs maintainer scripts such as preinst, unpacks the payload, handles replacement of an older version if necessary, and then configures the package.

The following command only does the unpacking step:

  
  

dpkg --unpack package.deb

  
  

This installs the package files but deliberately leaves the package unconfigured.

Then:

  
  

dpkg --configure package

  
  

finishes configuring it, mainly adding conffiles (configurations files) and running postinst script.

We can configure every unpacked-but-unconfigured package with:

  
  

sudo dpkg --configure -a

  
  

or:

  
  

sudo dpkg --configure --pending

  
  

So "installed" in dpkg terminology means the package has been correctly unpacked and configured.

Removal has two levels, just like we already saw with APT.

  
  

sudo dpkg -r foo

  
  

or:

  
  

sudo dpkg --remove foo

  
  

removes the installed files but retains dpkg-managed configuration files (conffiles).

To remove those configuration files too:

  
  

dpkg -P foo

  
  

or:

  
  

dpkg --purge foo

  
  

That can also purge a package that was already removed but still has residual configuration state.

Other files created dynamically by package scripts may need to be cleaned by the package’s postrm script (but the postrm and prerm package script are also run by dpkg --remove pkg, that's not just the same layer). Btw, user files under $HOME are not automatically deleted by a purge.

How do we make the distinction between package configuration files and the total amount of the files the package can dynamically create ?

A Debian binary package can declare certain shipped files as conffiles using a conffiles metadata file inside the .deb control archive. During package creation, this is typically debian/conffiles; after installation, dpkg records the information in its local database.

Here's what happen with dpkg --remove pkg:

  
  

1. runs prerm remove

2. removes the package’s normal installed files
   (but keeps conffiles)

3. runs postrm remove

  
  

And with dpkg --purge pkg:

  
  

1. runs prerm purge

2. removes the package’s normal installed files
   (but keeps conffiles)

3. runs postrm purge

  
  

In general, postrm purge / prerm purge delete more files than postrm remove and prerm purge.

Because postrm/prerm purge is the deeper cleanup path: it is where the package can remove generated config, package-specific state, caches, databases, or other leftovers that should disappear only on a full purge.

dpkg also exposes the selection-state machinery we just studied through apt-mark.

For example:

  
  

dpkg --get-selections

  
  

shows states such as:

  
  

install
hold
deinstall
purge

  
  

On my system:

  
  

dpkg --get-selections | head -n 30

  
  

Returns:

  
  

7zip						install
accountsservice					install
acl						install
adb						install
adduser						install
adwaita-icon-theme				install
aglfn						install
alacritty					install
alsa-base					install
alsa-topology-conf				install
alsa-ucm-conf					install
alsa-utils					install
amd64-microcode					install
anacron						install
android-libbase:amd64				install
android-libboringssl:amd64			install
android-libcutils:amd64				install
android-liblog:amd64				install
android-libziparchive:amd64			install
android-sdk-platform-tools-common		install
apg						install
app-install-data				install
apparmor					install
appstream					install
apt						install
apt-file					install
apt-utils					install
aptdaemon					install
aptdaemon-data					install
aptitude					install

  
  

We can save them:

  
  

dpkg --get-selections > selections.txt

  
  

and restore them with:

  
  

dpkg --set-selections < selections.txt

  
  

dpkg also proposes:

  
  

dpkg --clear-selections

  
  

That will mark all non-essential packages for removal (deinstall state).

dpkg knows that a package is essential or no by reading its metadata, indeed they expose this information in their metadata stanza with this field:

  
  

Essential: yes # or no

  
  

For package consistency/debugging, another useful action is:

  
  

dpkg -V

  
  

or:

  
  

dpkg --verify

  
  

which verifies package files against metadata where verification information is available.

This is a bit the equivalent of:

  
  

pacman -Qkk

  
  

We also have those commands:

  
  

dpkg --remove --pending
dpkg --purge --pending
dpkg --configure --pending

  
  

They apply the corresponding action to packages whose current dpkg state says that operation is pending.

But, wait a second, there is no apt-mark configure pkg, so why is that ? And why dpkg --install --pending does not exist?

This is where the distinction between selection state (apt-mark state thing) and package processing state matters.

configure is not a dpkg selection state, it's an actual processing action/state transition performed by dpkg on a package that has already been unpacked.

That is why this works:

  
  

dpkg --configure --pending

  
  

It finds every package that is already unpacked but not yet configured, and finish configuring it.

For removal and purge, --pending has a slightly different basis:

  
  

dpkg --remove --pending

  
  

Finds packages that are unpacked but whose selection state is deinstall, and remove them.

And:

  
  

dpkg --purge --pending

  
  

Finds packages that are unpacked or already removed but whose selection state is purge, and purge them.

Now the reason there is no:

  
  

dpkg --install --pending

  
  

is that dpkg cannot install a package merely from a package name or a selection state. To install, it needs the actual related .deb archive:

  
  

dpkg --install foo.deb

  
  

or:

  
  

dpkg --unpack foo.deb

  
  

The install selection state only says that the related package is desired to be installed.

The dpkg-query command familly

dpkg-query is the read/query side of dpkg’s local database. It does not install or remove anything.

First, we have the command that will list the installed packages on the system with:

  
  

dpkg-query -l

  
  

or:

  
  

dpkg-query --list

  
  

We can also limit the listed packages to only the one that matches given pattern(s), for example:

  
  

dpkg-query --list 'apt*' 'libc6*'

  
  

Which outputs something like:

  
  

ii  apt                   2.8.3                  amd64        commandline package manager
un  apt-config-icons      <aucune>               <aucune>     (aucune description n'est disponible)
un  apt-doc               <aucune>               <aucune>     (aucune description n'est disponible)
ii  apt-file              3.3                    all          search for files within Debian packages (command-line interface)
un  apt-show-versions     <aucune>               <aucune>     (aucune description n'est disponible)
un  apt-transport-https   <aucune>               <aucune>     (aucune description n'est disponible)
ii  apt-utils             2.8.3                  amd64        package management related utility programs
un  apt-verify            <aucune>               <aucune>     (aucune description n'est disponible)
un  apt-xapian-index      <aucune>               <aucune>     (aucune description n'est disponible)
ii  aptdaemon             1.1.1+bzr982-0ubuntu44 all          transaction based package management service
ii  aptdaemon-data        1.1.1+bzr982-0ubuntu44 all          data files for clients
ii  aptitude              0.8.13-5ubuntu5        amd64        terminal-based package manager
ii  aptitude-common       0.8.13-5ubuntu5        all          architecture independent files for the aptitude package manager
un  aptitude-doc          <aucune>               <aucune>     (aucune description n'est disponible)
un  aptitude-doc-en       <aucune>               <aucune>     (aucune description n'est disponible)
ii  aptkit                1.0.7                  all          transaction based package management service
ii  libc6:amd64           2.39-0ubuntu8.8        amd64        GNU C Library: Shared libraries
ii  libc6:i386            2.39-0ubuntu8.8        i386         GNU C Library: Shared libraries
un  libc6-amd64           <aucune>               <aucune>     (aucune description n'est disponible)
ii  libc6-dbg:amd64       2.39-0ubuntu8.8        amd64        GNU C Library: detached debugging symbols
un  libc6-dbgsym          <aucune>               <aucune>     (aucune description n'est disponible)
ii  libc6-dev:amd64       2.39-0ubuntu8.8        amd64        GNU C Library: Development Libraries and Header Files
un  libc6-dev-amd64-cross <aucune>               <aucune>     (aucune description n'est disponible)
un  libc6-dev-bin         <aucune>               <aucune>     (aucune description n'est disponible)
un  libc6-dev-i386        <aucune>               <aucune>     (aucune description n'est disponible)
ii  libc6-i386            2.39-0ubuntu8.8        amd64        GNU C Library: 32-bit shared libraries for AMD64
un  libc6-i686            <aucune>               <aucune>     (aucune description n'est disponible)
un  libc6-mips32          <aucune>               <aucune>     (aucune description n'est disponible)

  
  

The confusing columns is the first one.

Indeed, it represent the state of the related package.

The status colun is composes of 2 or 3 characters.

The first one indicates the desired state, the second indicates the current effective state and the third one, if it exists, represents the error flag.

Letter State Meaning
n Not-installed Package is not installed.
c Config-files Package payload is gone, but config files and/or postrm cleanup state remain.
H Half-installed Installation/unpacking started but failed before completing.
U Unpacked Files were unpacked from the .deb, but package configuration has not happened yet.
F Half-configured Configuration started, but failed before completing.
W Triggers-awaited This package is waiting for another package's trigger processing to complete.
t Triggers-pending This package itself has pending trigger work to process.
i Installed Package is correctly unpacked and configured.

The difference between H, U, and F is easiest to see as a lifecycle:

  
  

.deb
 |
 | unpack starts
 v
Half-installed (H)      <- unpack/install process failed midway
 |
 | unpack completed
 v
Unpacked (U)            <- files are there, but configuration not done
 |
 | configuration starts
 v
Half-configured (F)     <- postinst/configuration failed midway
 |
 | configuration completed
 v
Installed (i)

  
  

So H means the installation/unpacking phase itself is incomplete. For example, dpkg may have started replacing files and then something failed.

U is actually a clean intermediate state: unpacking succeeded, but configuration still needs to run.

F means unpacking succeeded and configuration was attempted but the latter failed.

That is why:

  
  

dpkg --configure --pending

  
  

is relevant to packages in states such as U and F: the package files are already present, and dpkg can try to finish the configuration phase.

c is also worth understanding carefully.

Indeed, we can often see the following couple:

  
  

rc  foo

  
  

meaning:

  
  

r = desired state: remove/deinstall
c = actual state: config-files

  
  

So the program itself is gone, but dpkg still retains enough package state to preserve or later purge configuration.

Now, we have to explain the "triggers" thing.

Imagine packages A and B and that they both uses some files under the same directory / same ressources but that the latter initially belongs to B on the system.

And that A must modify something under the shared ressources.

Does A do the work itself?

No, because the work belongs logically to B, not to A. B owns the shared resource, cache, database, index, registry, etc., and knows how to rebuild/update it.

This lets many packages notify the same interested package, and dpkg can defer and coalesce the work instead of rebuilding the same resource repeatedly.

Now the states:

  
  

t = triggers-pending
W = triggers-awaited

  
  

are:

  
  

B -> t (triggers-pending)
A -> W (triggers-awaited)

  
  

So t is from B’s point of view, not A’s.

B says:

“I have been triggered; I have pending trigger work to execute.”

While W is from A's point of view, it says:

"I caused trigger work in B and I am waiting for B to finish it before dpkg considers me completely installed."

Then, the following command:

  
  

dpkg --triggers-only --pending

  
  

Tells all the packages that have been notified of some work to do by others packages to perform it.

To list the fies a package have installed (owns), we do:

  
  

dpkg-query -L pkg

  
  

or:

  
  

dpkg-query --listfiles pkg

  
  

We can input multiple packages at once of course:

  
  

dpkg-query --listfiles pkgA pkgB

  
  

For example:

  
  

dpkg-query --listfiles openssh-client apt

  
  

This is the equivalent of:

  
  

pacman -Ql pkg

  
  

This is also the local variant of:

  
  

apt-file show pkg

  
  

But it only accepts explicit package names. (not RegEx neither "*").

We also have the local equivalent of:

  
  

apt-file search /path/to/some/file

  
  

Which is:

  
  

dpkg-query --search /path/to/some

  
  

or:

  
  

dpkg-query -S /path/to/some

  
  

It does not accept RegEx expression as apt-file search but accepts *, for example:

  
  

sudo dpkg-query -S /usr/bin/bash*

  
  

Returns (onmy system):

  
  

bash: /usr/bin/bash
bash: /usr/bin/bashbug

  
  

Now, we have a command which, at first glance, does the same thing than dpkg-query -l, which is:

  
  

dpkg-query -W

  
  

But, the great thing with the -W variant, is that we can control the output format with -f or --showformat.

This is super usefull in scripts.

Example:

  
  

dpkg-query -W -f='${Package}\t${Version}\t${Architecture}\n' | head

  
  

Returns this on my system:

  
  

7zip	23.01+dfsg-11	amd64
accountsservice	23.13.9-2ubuntu6.1	amd64
acl	2.3.2-1build1.1	amd64
adb	1:34.0.4-1build3	amd64
adduser	3.137ubuntu1	all
adwaita-icon-theme	46.0-1	all
aglfn	1.7+git20191031.4036a9c-2	all
alacritty	0.13.2-1ubuntu1	amd64
alsa-base	1.0.25+dfsg-0ubuntu7	all
alsa-topology-conf	1.2.5.1-2	all

  
  

Here are all the value you can use:

  
  

${Package}
${Version}
${Architecture}
${Status}

${Maintainer}
${Priority}
${Section}
${Installed-Size}
${Homepage}

${Essential}
${Protected}
${Multi-Arch}

${Depends}
${Pre-Depends}
${Recommends}
${Suggests}
${Enhances}

${Provides}
${Conflicts}
${Breaks}
${Replaces}

${Description}
${Conffiles}

${Source}
${Origin}

  
  

The dpkg-deb command familly

dpkg-deb is the tool for working directly with .deb archive files. It does not install packages into the system database; it inspects, extracts, and builds Debian binary archives.

Quick remainder, a .deb is an ar archive containing roughly:

  
  

package.deb
|-- debian-binary
|-- control.tar.*
|-- data.tar.*

  
  

First, we can query infromations from a .deb file with:

  
  

dpkg-query -I file.deb

  
  

or:

  
  

dpkg-query --info file.deb

  
  

For example:

  
  

dpkg-deb --info lz4_1.9.4-1build1.1_amd64.deb

  
  

Returns:

  
  

 nouveau paquet Debian, version 2.0.
 taille 93790 octets : archive de contrôle=757 octets.
     842 bytes,    21 lines      control
     362 bytes,     6 lines      md5sums
 Package: lz4
 Version: 1.9.4-1build1.1
 Architecture: amd64
 Maintainer: Ubuntu Developers <ubuntu-devel-discuss@lists.ubuntu.com>
 Installed-Size: 232
 Depends: libc6 (>= 2.34), liblz4-1 (= 1.9.4-1build1.1)
 Conflicts: liblz4-tool (<< 1.8.0-1)
 Replaces: liblz4-tool (<< 1.8.0-1)
 Provides: liblz4-tool
 Section: utils
 Priority: optional
 Multi-Arch: foreign
 Homepage: https://github.com/lz4/lz4
 Description: Fast LZ compression algorithm library - tool
  LZ4 is a very fast lossless compression algorithm, providing compression speed
  at 400 MB/s per core, scalable with multi-cores CPU. It also features an
  extremely fast decoder, with speed in multiple GB/s per core, typically
  reaching RAM speed limits on multi-core systems.
  .
  This package contains files that is tool using liblz4.
 Original-Maintainer: Nobuhiro Iwamatsu <iwamatsu@debian.org>

  
  

Here, we see the archive summary, which is this part:

  
  

 nouveau paquet Debian, version 2.0.
 taille 93790 octets : archive de contrôle=757 octets.
     842 bytes,    21 lines      control
     362 bytes,     6 lines      md5sums


  
  

And the control stanza.

If we just want the latter, we can precise it:

  
  

dpkg-deb --info lz4_1.9.4-1build1.1_amd64.deb control

  
  

In fact, the last argument is the file inside control.tar.* we temporary extract and display its content.

For example, we could have done:

  
  

dpkg-deb --info lz4_1.9.4-1build1.1_amd64.deb postinst

  
  

If postinst directly inside control.tar.* would exist.

Then, there is the equivalent of:

  
  

tar -tvf file.tar

  
  

That displays the files that will be extracted (those who lives under data.tar.*), with:

  
  

dpkg-deb -c file.deb

  
  

For example:

  
  

dpkg-deb -c lz4_1.9.4-1build1.1_amd64.deb

  
  

That outputs:

  
  

drwxr-xr-x root/root         0 2024-08-09 04:33 ./
drwxr-xr-x root/root         0 2024-08-09 04:33 ./usr/
drwxr-xr-x root/root         0 2024-08-09 04:33 ./usr/bin/
-rwxr-xr-x root/root    202992 2024-08-09 04:33 ./usr/bin/lz4
drwxr-xr-x root/root         0 2024-08-09 04:33 ./usr/share/
drwxr-xr-x root/root         0 2024-08-09 04:33 ./usr/share/doc/
drwxr-xr-x root/root         0 2024-08-09 04:33 ./usr/share/doc/lz4/
-rw-r--r-- root/root      3275 2022-08-28 05:00 ./usr/share/doc/lz4/copyright
drwxr-xr-x root/root         0 2024-08-09 04:33 ./usr/share/man/
drwxr-xr-x root/root         0 2024-08-09 04:33 ./usr/share/man/man1/
-rw-r--r-- root/root      3464 2024-08-09 04:33 ./usr/share/man/man1/lz4.1.gz
-rw-r--r-- root/root      3464 2024-08-09 04:33 ./usr/share/man/man1/lz4c.1.gz
-rw-r--r-- root/root      3464 2024-08-09 04:33 ./usr/share/man/man1/lz4cat.1.gz
-rw-r--r-- root/root      3464 2024-08-09 04:33 ./usr/share/man/man1/unlz4.1.gz
lrwxrwxrwx root/root         0 2024-08-09 04:33 ./usr/bin/lz4c -> lz4
lrwxrwxrwx root/root         0 2024-08-09 04:33 ./usr/bin/lz4cat -> lz4
lrwxrwxrwx root/root         0 2024-08-09 04:33 ./usr/bin/unlz4 -> lz4
lrwxrwxrwx root/root         0 2024-08-09 04:33 ./usr/share/doc/lz4/changelog.Debian.gz -> ../liblz4-1/changelog.Debian.gz

  
  

Now, when you want to extract the .deb/data.tar.* file to the output dir for example, you do:

  
  

dpkg-deb -x package.deb output/

  
  

Or:

  
  

dpkg-deb --extract package.deb output/

  
  

Then, we have:

  
  

dpkg-deb -X package.deb output/

  
  

Which is the verbose version: same extraction, but it prints the files as they are extracted.

And to extract the files that lives inside control.tar.*, we do:

  
  

dpkg-deb -e file.deb output/

  
  

Quick remainder, control.tar.* can contain:

  
  

control-dir/
├── control # always
├── conffiles
├── md5sums # always
├── preinst
├── postinst
├── prerm
└── postrm

  
  

And to extract both at the same time, we use the --raw-extract or -R command:

  
  

dpkg-deb -R file.deb output/

  
  

or:

  
  

dpkg-deb --raw-extract file.deb output/

  
  

Which makes approximately the following filesystem architecture:

  
  

output/
|-- DEBIAN/
|   |-- control
|   |-- postinst
|   |-- ...
|-- usr/
|   |-- ...
|-- etc/
    |-- ...

  
  

Also, to query special fields from the control metadata file, we do:

  
  

dpkg-deb -f file.deb FIELDNAME1 FIELDNBAME2 ...

  
  

For example:

  
  

dpkg-deb -f lz4_1.9.4-1build1.1_amd64.deb Version Package

  
  

Returns:

  
  

Version: 1.9.4-1build1.1
Package: lz4

  
  

If we don't precise any field, it willl behave like dpkg-deb -I file.deb control.

Now, the reverse operation of dpkg-deb -R file.deb output/ that means building a .deb file from a package files.

For example, if we have:

  
  

foo-package/
|-- DEBIAN/
|   |-- control
|   |-- postinst
|-- usr/
    |-- bin/
        |-- foo

  
  

We do:

  
  

dpkg-deb --build foo-package

  
  

or:

  
  

dpkg-deb -b foo-package

  
  

Which produces:

  
  

foo-package.deb

  
  

Now, if you explicit an existing directory as the output like:

  
  

dpkg-deb -b foo-package out-dir/

  
  

It will produce:

  
  

out-dir/foo-package_version_architecture.deb

  
  

But you can also explicit an output filename like:

  
  

dpkg-deb --build foo-package foo-package-deb.deb

  
  

We can also whoose the compression of the inner archives (data.tar.* and control.tar.*) with the -Z flag:

  
  

-Z xz
-Z gzip
-Z zstd
-Z none

  
  

And the compression level with -z (from 1 to 9).

We can also limit the threads that will be used for the compressions (for the one that supports it) with --threads-max= option.

So, for example:

  
  

dpkg-deb -b -Z xz -z 8 --threads-max=4 foo-package out

  
  

The dpkg-source familly command

After looking at dpkg-deb, which manipulates binary .deb archives, the next logical tool is dpkg-source.

dpkg-source works one level earlier in the Debian packaging pipeline: it packs and unpacks Debian source packages. A source package is generally described by a .dsc file and accompanied by one or more source archives such as an upstream orig.tar.* archive and a Debian-specific debian.tar.* archive (as we saw earlier). dpkg-source knows how to read that metadata, reconstruct the debianized source tree, apply Debian patches when required, and build the source-package files again from a prepared source tree.

First, there is the unpacking/extracting step with the -x flag.

So here you have the source package files in the same directory like:

  
  

~/src/openssh/
|-- openssh_9.6p1-3ubuntu13.dsc
|-- openssh_9.6p1.orig.tar.gz
|-- openssh_9.6p1.orig.tar.gz.asc
|-- openssh_9.6p1-3ubuntu13.debian.tar.xz

  
  

The .asc file is not another source archive. It is a signature that can be used to verify that the tarball really came from the expected upstream signer and was not modified afterward.

Then, from this directory we can run:

  
  

dpkg-source -x openssh_9.6p1-3ubuntu13.dsc

  
  

Indeed, the .dsc contains entries naming and hashing of the companion files.

It then finds them next to the .dsc and reconstructs:

  
  

openssh-9.6p1/
|-- upstream source code
|-- debian/
    |-- control
    |-- rules
    |-- changelog
    |-- patches/
    |-- ...

  
  

Note that openssh-9.6p1 is the default output directory in this context.

But we could have explicit an output directory like:

  
  

dpkg-source -x openssh_9.6p1-3ubuntu13.dsc openssh-tree

  
  

Quick remainder.

For a modern package using:

  
  

Format: 3.0 (quilt)

  
  

the process is conceptually:

  
  

orig.tar.*
    | unpack upstream source
    V
debian.tar.*
    | add debian/ packaging directory
    V
debian/patches/series
    | apply Debian patches
    V
final debianized source tree

  
  

So it's not just tar xf; it understands the Debian source-package format and reconstructs the source tree accordingly.

Now, we also have this option for the extraction (--require-valid-signature):

  
  

dpkg-source --require-valid-signature -x package.dsc

  
  

This will check if the .dsc file have an OpenPGP signature, and if yes, if it's valid.

Indeed, it can verify if that's valid because internally dpkg uses the trusted public keys to verify .dsc signatures.

If there is no signature or that it's not valid, then the extraction is aborted.

We can also explicitly provide a keyring:

  
  

dpkg-source \
  --signer-certs=/path/to/keyring.gpg \
  --require-valid-signature \
  -x package.dsc

  
  

The opposite exists:

  
  

dpkg-source --no-check -x package.dsc

  
  

which disables signature/checksum checks during extraction.

By default, the extraction will verify the source-package checksums recorded in the .dsc, but it does not require that the .dsc itself have a valid trusted OpenPGP signature.

We also have:

  
  

dpkg-source --require-strong-checksums -x package.dsc

  
  

which requires strong checksums such as SHA-256.

The reverse operation is:

  
  

dpkg-source -b source-directory

  
  

For example:

  
  

dpkg-source -b openssh-9.6p1

  
  

You get it, openssh9.6p1 is a debianized source tree.

dpkg-source determines the source format, commonly from:

  
  

debian/source/format

  
  

For example a common one is:

  
  

3.0 (quilt)

  
  

We can also explicitly select the source format:

  
  

dpkg-source --format='3.0 (quilt)' -b openssh-9.6p1

  
  

and then produces:

  
  

openssh_9.6p1-3ubuntu13.dsc
openssh_9.6p1.orig.tar.gz
openssh_9.6p1.orig.tar.gz.asc
openssh_9.6p1-3ubuntu13.debian.tar.xz

  
  

The location of the output is in fact the parent directory of openssh9.6p1.

There are also compression controls very similar to what you just saw with dpkg-deb:

  
  

dpkg-source -Zxz -z6 -b source-directory

  
  

While building, I can also tell that the default location of changelog and control have changed to a custom one (instead of /debian/*), for example if I have the following:

  
  

source-directory/
|-- packaging/
|   |-- control
|   |-- changelog
|-- src/

  
  

I do:

  
  

dpkg-source \
  -cpackaging/control \
  -lpackaging/changelog \
  -b source-directory

  
  

-c means control file, and -l means changelog file.

There is also the --include-binaries option:

  
  

dpkg-source --include-binaries -b source-directory

  
  

It's usefull when we apply a modification that a patch can not take in count such as adding/modifying a binary file in the upstream content for example.

It tells dpkg-source:

"If you find modified binary files that cannot be represented as quilt patches, include those files directly inside the generated debian.tar.*."

For example:

  
  

upstream orig.tar.*
|-- src/foo.c
|-- docs/logo.png       <- original version

  
  

We modify:

  
  

docs/logo.png

  
  

Now a text patch is unsuitable. With --include-binaries, we can generate the wanted debian.tar.* and orig.tar.* etcetera.

The opposite is:

  
  

dpkg-source --abort-on-upstream-changes -b source-directory

  
  

Which makes the build fail if there are changes to upstream files that have not been properly represented as Debian patches.

Now, when I have the debianized source tree thanks to dpkg-source -x file.dsc, I can ensure that the patches in debian/patches/ are applied with:

  
  

dpkg-source --before-build source-dir

  
  

source-dir is the debianized tree.

We’d use it mainly for debugging, experimentation... (because as we'll see dpkg-buildpackage will automatically handle it).

Now, we also have the matching operation that will restore the debianized tree to its prior state to what dpkg-source --before-build source-dir did to it.

This is the following command:

  
  

dpkg-source --after-build source-dir

  
  

Normally, dpkg-buildpackage also handles it.

Now, suppose we've unpacked a source package:

  
  

dpkg-source -x foo_1.2.3-1.dsc
cd foo-1.2.3

  
  

Then we manually edit an upstream file:

  
  

src/foo.c

  
  

At that point, our change exists in the working tree, but it is not yet represented in debian/patches/.

Now we run:

  
  

dpkg-source --commit .

  
  

For 3.0 (quilt), dpkg-source compares our current tree against the source state represented by the existing quilt patch series, finds the changes that are not already managed by quilt, generates a new patch, and integrates it into the patch system. If we do not supply a patch name, it asks you interactively for one.

For example:

  
  

dpkg-source --commit . fix-crash

  
  

may create:

  
  

debian/patches/fix-crash

  
  

and add:

  
  

fix-crash

  
  

to:

  
  

debian/patches/series

  
  

The dpkg-buildpackage command familly

dpkg-buildpackage is the build orchestrator of the dpkg toolchain.

In more elaborated terms, dpkg-source knows how to represent and reconstruct a Debian source package; debian/rules knows how that particular package should be built; and dpkg-buildpackage orchestrates the complete process around them, including dependency checks, build targets, binary/source package generation, build metadata, and signing.

So, if I get the source tree of a package through:

  
  

apt-get source pkg

  
  

That will download the source files AND unpack them.

Now we can enter the source tree and directly run:

  
  

dpkg-buildpackage

  
  

inside the root of the source tree.

Note that one source package can generate several .deb packages.

The latter are declared inside debian/control.

We'll take the openssh package as example.

Indeed, openssh defines several .deb such as:

  
  

openssh-client
openssh-server
openssh-sftp-server
ssh

  
  

They will be generated in the source tree parent directory like:

  
  

../openssh-client_<version>_amd64.deb
../openssh-server_<version>_amd64.deb
../openssh-sftp-server_<version>_amd64.deb
../ssh_<version>_all.deb

  
  

And with metadata:

  
  

../openssh_<version>_amd64.buildinfo
../openssh_<version>_amd64.changes

  
  

As we saw earlier, the package-sepcific build procedure is defined through debian/rules.

A simplified build flow is:

  
  

dpkg-buildpackage
        |
        |-- prepare build environment
        |
        |-- dpkg-source --before-build
        |
        |-- verify Build-Depends / Build-Conflicts
        |
        |-- debian/rules clean
        |
        |-- dpkg-source -b
        |       |
        |       |-- generate/update source package artifacts
        |           such as .dsc and .debian.tar.*
        |           and reference/reuse the .orig.tar.*
        |
        |-- debian/rules build
        |
        |-- debian/rules binary
        |       |
        |       |-- final .deb archives are built
        |
        |-- generate .buildinfo
        |-- generate .changes

  
  

And yess, we literally have call like:

  
  

debian/rules clean
debian/rules build
debian/rules binary

  
  

Because remember, debian/rules usually starts with something like:

  
  

#!/usr/bin/make -f

  
  

So, it's effectively run as:

  
  

make -f debian/rules build

  
  

Now, what are the different specific "build" commands ?

  1. clean resets the source tree so we start from a known state. It removes generated files, temporary staging directories, and anything left by previous build/binary runs, while leaving already-produced package files in the parent directory alone.

Conceptually:

  
  

before clean:

foo-1.0/
├── src/
├── build/
│   ├── foo.o
│   └── foo
├── debian/
│   ├── foo/
│   │   └── usr/bin/foo
│   └── ...
└── ...

after:

debian/rules clean

foo-1.0/
├── src/
├── debian/
└── ...

  
  
  1. build will compile and configure the programms but won't create the final .deb packages.

  2. binary is the packaging phase. It'll take the results of the build and create all binary packages desired by this source package.

So, yess in the binary phase, one or more dpkg-deb --build command are run agains staging directories.

Those staging directories provides all the informations to generate a .deb, so their structure is therefore like (as seen before):

  
  

foo-package/
|-- DEBIAN/
|   |-- control
|   |-- postinst
|-- usr/
    |-- bin/
        |-- foo

  
  

The interesting question is: who creates those staging directories and fills DEBIAN/?

Usually, modern packages use debhelper.

  
  

Helpers do things such as:
dh_auto_install
-> install upstream-built files into staging

dh_install
-> distribute files among debian/foo/, debian/foo-doc/, etc.

dh_installdocs
-> install docs

dh_installdeb
-> prepare DEBIAN/ maintainer scripts/conffile metadata

dh_gencontrol
-> generate DEBIAN/control

dh_md5sums
-> generate DEBIAN/md5sums

dh_builddeb
-> call dpkg-deb for each binary-package staging tree

  
  

Now, the question is why does it kind of regenrate the source package files it is run on with the dpkg-source command ?

The key idea is reproducibility and traceability:

  
  

current source tree
        |
        V
source package files
        +
binary package files

  
  

A full build wants both sides to correspond to the same package version and source state.

If we changed:

  
  

debian/control
debian/changelog
debian/patches/*
...

  
  

and dpkg-buildpackage only emitted new .deb files, we end up with new binaries but no matching source package that represents exactly what was built.

So by default it rebuilds the source package too.

A normal full dpkg-buildpackage rebuilds the Debian source package files from the source tree it is run in.

To separate the steps, we have the -b flag:

  
  

dpkg-buildpackage -b .

  
  

That will skip the source files rebuild and only build the .deb files.

The opposite is:

  
  

dpkg-buildpackage -S

  
  

That only builds the source package files.

So in the flow it stops here:

  
  

dpkg-buildpackage
        |
        |-- prepare build environment
        |
        |-- dpkg-source --before-build
        |
        |-- verify Build-Depends / Build-Conflicts
        |
        |-- debian/rules clean
        |
        |-- dpkg-source -b
        |       |
        |       |-- generate/update source package artifacts
        |           such as .dsc and .debian.tar.*
        |           and reference/reuse the .orig.tar.*
        |
        | <-- STOPS
        |
        |-- debian/rules build
        |
        |-- debian/rules binary
        |       |
        |       |-- final .deb archives are built
        |
        | <-- CONTINUES
        |
        |-- generate .buildinfo
        |-- generate .changes

  
  

Now, we also can distinguish packages relative to if they are architecture-dependent.

Indeed:

  
  

dpkg-buildpackage -B

  
  

Build only architecture-dependent binary packages, meaning packages declared with:

  
  

Architecture: any

  
  

These usually refers to compiled code, so the resulting package is architecture-specific, for example:

  
  

foo_1.0-1_amd64.deb

  
  

Then:

  
  

dpkg-buildpackage -A

  
  

Build only architecture-independent binary packages, those are packages declared with:

  
  

Architecture: all

  
  

These are typically documentation, scripts, data files, etc., producing something like:

  
  

foo-doc_1.0-1_all.deb

  
  

For local experiments, signing is often unnecessary, so we can use:

  
  

dpkg-buildpackage -us -uc

  
  

where:

  • -us -> do not sign the source package (.dsc file)

  • -uc -> do not sign the .changes/.buildinfo metadata

We can also skip the initial clean step:

  
  

dpkg-buildpackage -nc

  
  

or clean the source tree after a successful build:

  
  

dpkg-buildpackage -tc

  
  

Parallel compilation can be requested with:

  
  

dpkg-buildpackage -j8

  
  

Finally, dpkg-buildpackage checks the source package's build dependencies before starting. Those are declared in debian/control through fields such as:

  
  

Build-Depends:
Build-Conflicts:

  
  

If they are missing, the build normally stops.

As seen earlier, APT can install those dependencies beforehand with:

  
  

apt-get build-dep openssh

  
  

Here, openssh is the source package name.

Formany packages, their source-package name are the same, but for those that produces several .deb, they differ.

openssh is a good example, because that's the source package name, but it produces binaries such as:

  
  

openssh-client
openssh-server
openssh-sftp-server
ssh

  
  

Now, when the .deb are generated, we can finally install them, for example:

  
  

sudo dpkg -i ../openssh-server_9.6p1-3ubuntu13_amd64.deb

  
  

Conclusion

pacman, APT and dpkg show the same core ideas behind a good package-management system: clear separation of responsibilities, composable tools, explicit metadata and reproducible builds.

Pacman exposes these responsibilities through command families, while Debian goes further by splitting repository management, local package management and package building across several tools.

That separation gives users control: packages can be queried, inspected, extracted, rebuilt and installed independently instead of everything being hidden behind one command.