Debian: How preferences decide what will be installed and apt resolves

7 mindebianaptpinningapt-preferencespin-prioritybackportsunattended-upgradespackage-managementunstableexperimental

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:

sh
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).

sh
$ 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:

perl
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:

perl
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:

sh
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.