Debian: How preferences decide what will be installed and apt resolves
TL;DR
Apt pinning isn’t about pinning — it’s about setting preferences. Declare which repo has a higher priority, block packages with negative priorities, and force downgrades with priorities exceeding a thousand.
Pinning a package, say what?!
Debian has something called pinning. You can pin packages to versions, channels
and force deinstallations and upgrades through it. You can pin a package
through a /etc/apt/preferences and via /etc/apt/preferences.d. Personally I
use the former for everything that is related to (stock) Debian itself and the
latter is used for other repos or non-defaults. For example, on my unstable
machine I have a ruleset that is defined in /etc/apt/preferences and
everything else goes into /etc/apt/preferences.d.
Before we go deeper into the abyss, let’s first describe how apt works. Apt decides based on a priority if it will install a package or not, and from which repo. By default all repos have one priority, 500. An installed package has the priority of 100. If Debian pushes an newer version of a package to one of the repos and you have it configured, installed, and the version of the package on the repo is higher than your installed package, apt will upgrade this package. This is the most simple rule apt has. With pinning you can alter the priorities of a repo and packages.
Pinning is a sort of misnomer, it is more a preference, hence the preferences file, for a repository or package. The way you set it by using a pin. In other words, you set priority to tell apt which repo or package you prefer. We call it pinning for some reason, but it is all about preferences (and priorities).
In man apt_preferences you’ll find a complete list of all the
Pin-Priorities, the default for a repository is 500. That is usually the
repository of the distribution you have installed. Anything higher will
override that repositories preference and anything lower will reduce the
preference for that repository. A preference of 100 is often reserved for the
preference of an installed package. Debian by default will also set
-backports to 100 to ensure that it doesn’t automatically upgrade your system
to backported packages. If you set a preference of over 1000 you will force apt
to install that preference, even if it means downgrading a package. Setting it
to -1 will mean it will not install that package, and potentially even
uninstall it. You can also set it to 0, I’ve never tried it, the behaviour is..
undefined. Just use any other value than 0.
On any given unstable machine I own and operate on you will see the following preferences file:
$ cat /etc/apt/preferences
# Preferences for unstable/experimental debian releases
Package: *
Pin: release a=experimental,o=Debian
Pin-Priority: 100
Package: *
Pin: release a=unstable,o=Debian
Pin-Priority: 900
Package: *
Pin: release a=testing,o=Debian
Pin-Priority: 500
Package: *
Pin: release a=stable,o=Debian
Pin-Priority: 10
Package: *
Pin: release a=stable-updates,o=Debian
Pin-Priority: 10
Package: *
Pin: release a=stable-security,o=Debian
Pin-Priority: 10
Package: *
Pin: release a=oldstable,o=Debian
Pin-Priority: 10
Package: *
Pin: release a=oldstable-security,o=Debian
Pin-Priority: 10
Package: *
Pin: release a=oldoldstable,o=Debian
Pin-Priority: 10
As you can see unstable has 900, testing 500 and everything else is set to 10 with the exception of experimental. I do this for several reasons. One, when I help people I want to know what package versions are available on the various Debian releases. As an unstable user I’m also using testing as an escape hatch if a bug occurs on an unstable package version. I can resolve the issue by installing the testing version and reduce the impact a bug has on me. Experimental is when I know a package might be of interest to me so I can install it. Some people use it to track newer kernels.
You might recall I said versions win if the priority is the same, so why am I
setting unstable to 900? Yes, you are right, unstable will win the fight any
day of the week if both testing and unstable are set to 500. But I want to
indicate to the world, e.g., bugreport that I’m running unstable.
If you ever want to find out what bugreport thinks you prefer:
apt-cache policy | \
awk '/o=(Debian|Ubuntu|Debian Ports)/{print prev}{prev=$0}' | \
sort -nr | head -1 | awk '{split($3, arr, "/"); print arr[1]}'
Ok, now what?
So what can you do with pinning or setting your preferences. Well, quite a lot
actually. Some things may be frowned upon, some things may be clever hacks. But
however you want to use it, it will give you insights in what packages will be
installed and from which repo. You can see this by using apt-cache policy
(and on newer Debians you can also just use apt policy, but I’ll refer to
apt-cache policy in this article).
$ apt-cache policy base-files
base-files:
Installed: 14
Candidate: 14
Version table:
*** 14 900
900 https://deb.debian.org/debian unstable/main amd64 Packages
500 https://deb.debian.org/debian testing/main amd64 Packages
100 /var/lib/dpkg/status
13.8+deb13u4 10
10 https://deb.debian.org/debian stable/main amd64 Packages
12.4+deb12u13 10
10 https://deb.debian.org/debian oldstable/main amd64 Packages
11.1+deb11u11 10
10 https://deb.debian.org/debian oldoldstable/main amd64 Packages
So, what do you see here? First of all, it will tell you which version is installed, what the candidate is and where it will get a specific version from.
You see the 900, 500 and 100 priorities on version 14 for unstable and testing and the 100 for the installed one. You also see which versions are available on stable, oldstable, and oldoldstable.
If you change the preferences files, you will see changes and possibly change the candidate for installation.
The more interesting thing comes when you run apt-cache policy without any
arguments:
$ apt-cache policy
# [snip]
900 https://deb.debian.org/debian unstable/contrib i386 Packages
release o=Debian,a=unstable,n=sid,l=Debian,c=contrib,b=i386
origin deb.debian.org
# [snip]
This tells you how the repo is configured, but also some information on how to
pin the package. release o=Debian,a=unstable,n=sid,l=Debian,c=contrib,b=i386
is the line you are interested in if you want to exclude the architecture i386
in a preferences file. If you want to pin versions of Debian stable (or
oldstable), you’ll want release v=13.4,o=Debian for example. And in addition
you can also pin on the origin, e.g.origin deb.debian.org. You can see the
following examples where I give preference to Google and NodeJS for specific
packages:
Package: *
Pin: origin dl.google.com
Pin-Priority: 900
NodeJS has some limitations but I’m very much interested in their nodejs release:
Package: *
Pin: origin deb.nodesource.com
Pin-Priority: 10
Package: nodejs
Pin: origin deb.nodesource.com
Pin-Priority: 900
And steam I only want from the unstable branch:
Package: steam-installer
Pin: release o=Debian,a=unstable
Pin-Priority: 1001
Unattended Upgrades too
Keep in mind, these values aren’t just for pinning, in
/etc/apt/50unattended-upgrades this o=Foo notation is also used, although
the origin becomes site:
Unattended-Upgrade::Origins-Pattern {
"a=mozilla,n=mozilla";
"o=Docker,a=bookworm";
"o=Google LLC,a=stable,n=stable";
"site=deb.nodesource.com";
"site=updates.signal.org";
};
Unattended upgrades also spells them out:
origin=Debian,codename=${distro_codename},label=Debian-Security
Package pinning
You already saw * for a full range of packages from a repository, and there
is more. You can also add pins for a group of packages:
Package: systemd systemd:i386
Pin: version 258~rc2-2
Pin-Priority: -1
And you can use wildcards too:
Explanation: Unknown why python 3.13 is upgraded because python3 depends on 3.9
Package: python3.13* python3-* libpython*
Pin: version 3.13.11-1+b1
Pin-Priority: -1
And you can also see it has an official way to even document the Pin without
having to use # comments by using Explanation: reasons here.
And apt-cache policy also shows these pinned package rules:
Pinned packages:
docker-compose-plugin -> 2.24.1-1~debian.12~bookworm with priority -1
Backports
Even if you never are going to use pinning, there is one thing that is
important to know if you use backports. Backports by default runs at 100. Which
is the same as the installed package. Any installed package from backports will
be upgraded by unattended-upgrades (if you configure it) or you if you run
apt-get upgrade (or equivalent) if a higher version exists on backports. This
is because if a priority of package is the same but the version is higher
that that version will win. You can only make a version lose when you set
another priority higher. So for example if you don’t want upgrades from Debian
backports you need to set the priority of backports to anything less than a
100, but above 0. You can still install packages from backports, but it will
never be a candidate for upgrades.