Right, so you’ve decided to get fancy. Or more likely, you’ve been burned. Something tried to upgrade and it broke your perfectly curated setup. Maybe it was a new version of nginx that changed a critical config file syntax, or a dependency for your custom-built application got a backwards-incompatible update. Welcome to the big leagues. This is where we move from just letting apt do whatever it wants to telling it exactly what we expect of it. We’re going to talk about pinning and holding, the two primary methods for preventing specific packages from upgrading.

The Simple, Blunt Instrument: apt-mark hold

Let’s start with the easy one. When you just need to absolutely, positively stop a single package from being upgraded, ever, until you say so, you use hold. It’s the digital equivalent of putting a brick on the package’s head.

Think of it like this: apt-get upgrade is a bouncer checking a list. A package on hold is on that list with a big, red “DO NOT TOUCH” stamp next to it. The bouncer just skips it. It’s incredibly simple and effective.

You hold a package with:

sudo apt-mark hold package_name

And when you’ve come to your senses (or finally tested the new version), you unhold it with:

sudo apt-mark unhold package_name

To see your list of held packages, which is a fantastic idea for sanity checks:

apt-mark showhold

Why would you use this? It’s perfect for emergency stops. That docker-ce version that just dropped and is known to be borked? Hold it. The libc6 upgrade that you know will break three legacy apps you still need to run? Hold it. It’s your first line of defense. The pitfall? It’s all or nothing. The package is frozen in time, vulnerabilities and all, until you manually intervene. This is a tactical tool, not a strategic one.

The Precise, Surgical Tool: Pinning with apt-preferences

Now, hold is great, but it’s dumb. What if you don’t want to block a package forever, you just want to prioritize one repository over another? This is the classic use case for pinning. Say you’re running a mostly stable Debian bullseye system, but you need a newer version of ffmpeg that’s only in the bullseye-backports repo. You want to pull ffmpeg from backports, but you want everything else to stay firmly on the standard bullseye releases. This is where the /etc/apt/preferences file (and files in /etc/apt/preferences.d/) comes in.

Pinning works by assigning a priority (a number from 0 to 1001) to packages from different origins. The rules are simple: APT will always install the version with the highest priority. If priorities are equal, it installs the newest version. Here’s the cheat sheet:

  • 1001: required priority (core system stuff, you can’t override this even if you tried).
  • 100 to 999: preferred priorities. This is your working range.
  • 500: The default priority for all packages.
  • 100: The priority of the stable release.
  • 1 to 99: non-preferred priorities. You can use this to downgrade the importance of a repo.
  • <0: Effectively disables the package from that origin.

So, for our ffmpeg from backports example, we need to give packages from the backports origin a higher priority than 500, but only for the specific packages we want. We don’t want to wholesale upgrade our system from backports; that way lies madness.

Here’s how you’d craft a file, say /etc/apt/preferences.d/90_backports-ffmpeg:

Package: ffmpeg
Pin: release a=bullseye-backports
Pin-Priority: 600

Package: *
Pin: release a=bullseye-backports
Pin-Priority: 100

Let’s break down this masterpiece of passive aggression:

  1. The first stanza: “For the package ffmpeg, if it comes from the release where the suite (a=) is bullseye-backports, give it a nice high priority of 600.” This means APT will eagerly upgrade ffmpeg from backports.
  2. The second stanza: “For EVERY other package (*) from backports, give it a lowly priority of 100.” Since the default stable repo is at 500, this makes APT actively avoid installing anything else from backports unless it’s explicitly requested or there’s no other version. It brilliantly isolates the chaos.

You can see the brilliant, terrifying results of your pinning with:

apt-cache policy ffmpeg

This will show you all available versions and their assigned priorities. Your chosen version should have a priority of 600, making it the winner.

The designers made this syntax needlessly cryptic (a= for suite? n= for name? It’s an arcane incantation). The common pitfall is being too broad with your pin and accidentally pulling in half of Debian testing because you gave it a priority of 999. Always check your work with apt-cache policy.

So, to recap: use hold for a quick, total freeze. Use pinning when you need nuanced, repository-based control. Both are essential skills for keeping a system that’s both stable and useful, which is, after all, the whole point.