cheat sheet

apt-get Package Management

Debian and Ubuntu package management — update, install, remove, upgrade, and maintain packages with apt-get.

apt-get Package Management

What it is

apt-get is the command-line front-end to APT (Advanced Package Tool), the package manager for Debian-based Linux distributions including Debian, Ubuntu, Mint, and Raspberry Pi OS. Maintained as part of the Debian project, it resolves dependencies automatically and handles downloading, installing, upgrading, and removing packages from configured repositories. Reach for apt-get (or its friendlier sibling apt) whenever you need to install system software, apply security updates, or keep a Debian-family system in sync with its package mirrors.

Update & upgrade

apt-get update refreshes the local package index from all configured mirrors — it downloads metadata only, not packages. apt-get upgrade then installs newer versions of installed packages without adding or removing anything; apt-get dist-upgrade goes further by resolving dependency changes that require adding or removing packages.

bash
# Refresh the local package index (always run before upgrading/installing)
apt-get update

# Upgrade installed packages — won't remove or add packages
apt-get upgrade

# Full upgrade: installs new deps and removes conflicting packages
# Also allows transitioning to the next minor OS release (e.g. 22.04.1 → 22.04.2)
apt-get dist-upgrade

# One-liner: refresh then full-upgrade
apt-get update && apt-get dist-upgrade

Output: (none — exits 0 on success)

Install & remove

apt-get install downloads a package and all unmet dependencies in one step. apt-get remove uninstalls the binary but leaves configuration files behind — useful if you plan to reinstall; apt-get purge removes both the package and its system-wide config files, which is the right choice when you want a clean slate.

bash
# Install one or more packages (with dependencies)
apt-get install PACKAGE
apt-get install nginx curl git

# Install without recommended packages
apt-get install --no-install-recommends PACKAGE

# Remove a package (keeps config files)
apt-get remove PACKAGE

# Remove a package AND its system-wide configuration files
apt-get purge PACKAGE
apt-get remove --purge PACKAGE     # equivalent

# Fix broken dependency tree (run when apt complains about unmet deps)
apt-get -f install

Output: (none — exits 0 on success)

Download without installing

apt-get download fetches the .deb archive for a package into the current directory without installing it. This is useful for staging packages for offline or air-gapped installation on another machine.

bash
# Download .deb to current directory (no install)
apt-get download PACKAGE

# Download a package AND its important dependencies (useful for offline installs)
apt-get download firefox \
  $(apt-cache --important depends firefox | awk '{if(NR>1){printf("%s ", $2)}}')

Output: (none — exits 0 on success)

Cache maintenance

APT caches downloaded .deb files in /var/cache/apt/archives/. autoclean removes only packages that can no longer be downloaded (stale or superseded versions), while clean wipes the entire cache. autoremove targets a different problem: packages that were installed automatically as dependencies but are no longer needed by anything.

bash
# Remove .deb files that can no longer be downloaded (interrupted downloads)
apt-get autoclean

# Remove ALL cached .deb files and package index files
apt-get clean -s    # -s = simulate; omit to actually clean
apt-get clean

# Remove packages that were installed as dependencies but are no longer needed
apt-get autoremove

Output: (none — exits 0 on success)

Useful flags

FlagEffect
-yAssume yes to all prompts
-qQuiet — suppress progress output
--no-install-recommendsSkip recommended (not required) packages
-fFix broken installs
-s / --simulateDry-run: show what would happen
--reinstallReinstall already-installed package
-o Dir::Cache="…"Override cache directory

Configuration

APT reads its configuration from a layered tree under /etc/apt/. Knowing which file controls which behaviour saves time when debugging "why did APT do that?" — every command-line -o flag corresponds to a key in one of these files, and per-host policy lives here too.

PathPurpose
/etc/apt/sources.listClassic one-line repository list (legacy on Ubuntu 24.04+, optional everywhere)
/etc/apt/sources.list.d/*.listAdditional one-line repository files (one per third-party source)
/etc/apt/sources.list.d/*.sourcesdeb822-format repository stanzas — preferred on Debian 12+ and Ubuntu 24.04+
/etc/apt/apt.confTop-level APT options (rarely edited directly)
/etc/apt/apt.conf.d/*Per-feature config drop-ins (e.g. 50unattended-upgrades, 20auto-upgrades, 70debconf)
/etc/apt/preferencesTop-level pinning rules
/etc/apt/preferences.d/*Per-source/per-package pinning files
/etc/apt/keyrings/Signing keys for third-party repos referenced via Signed-By= (modern)
/etc/apt/trusted.gpg.d/*System-wide keyrings shipped by official keyring packages
/etc/apt/auth.conf.d/*Credentials for private repositories (mode 0600)
/var/lib/apt/lists/Cached release/package indexes downloaded by apt-get update
/var/cache/apt/archives/Downloaded .deb archives (cleaned by autoclean / clean)
/var/lib/dpkg/statusdpkg's installed-package database — never edit by hand
bash
# Dump the fully-resolved configuration APT will actually use
apt-config dump

# Show only one subtree (e.g. APT::Get::*)
apt-config dump APT::Get

# Show which config files were loaded, in order
apt-config dump | grep -i '^Dir::Etc'

# Inspect the merged repository list
apt-cache policy

Output (apt-config dump APT::Get):

text
APT::Get::Assume-Yes "false";
APT::Get::AutomaticRemove "false";
APT::Get::HideAutoRemove "false";
APT::Get::Download-Only "false";
APT::Get::Show-Upgraded "false";

Drop-ins under apt.conf.d/ are read in lexical order; later files override earlier ones. The conventional NN prefix (e.g. 50unattended-upgrades, 99local) controls precedence — keep local overrides at 99 so package updates don't trample them.

Silence config-file prompts in batch updates

bash
# Keep existing config files during unattended upgrades
apt-get update -o DPkg::Options::='--force-confold' && apt-get upgrade -y

Output: (none — exits 0 on success)

Find what's installed

dpkg -l shows the installation status of a specific package using a two-letter status code (ii means fully installed). apt-mark showmanual lists packages you explicitly installed — excluding auto-installed dependencies — which is the most useful list for recreating a system.

bash
# Show packages installed since log began
grep 'install ' /var/log/dpkg.log

# List manually installed packages
apt-mark showmanual

# Check if a specific package is installed
dpkg -l PACKAGE

Output (grep 'install ' /var/log/dpkg.log):

php-template
2026-04-20 09:14:22 install curl:amd64 <none> 7.88.1-10+deb12u5
2026-04-21 13:02:47 install git:amd64 <none> 1:2.39.2-1.1
2026-04-23 08:55:11 install ripgrep:amd64 <none> 13.0.0-4+b2

Output (apt-mark showmanual):

code
bat
curl
git
nginx
ripgrep
vim

Output (dpkg -l PACKAGE — e.g. dpkg -l nginx):

ini
Desired=Unknown/Install/Remove/Purge/Hold
| Status=Not/Inst/Conf-files/Unpacked/halF-conf/Half-inst/trig-aWait/Trig-pend
|/ Err?=(none)/Reinst-required (Status,Err: uppercase=bad)
||/ Name           Version            Architecture Description
+++-==============-==================-============-============================
ii  nginx          1.24.0-2           amd64        high performance web server

View the changelog before upgrading

apt-get changelog downloads and prints the Debian changelog for a package directly from the distribution's servers. Check it before upgrading a critical package to understand what changed and whether any breaking changes or new dependencies were introduced.

bash
apt-get changelog firefox

Output:

less
firefox (125.0+build2-0ubuntu0.24.04.1) noble; urgency=medium

  * New upstream release (https://www.mozilla.org/en-US/firefox/125.0/releasenotes/)

 -- Ubuntu Mozilla Team <ubuntu-mozillateam@ubuntu.com>  Tue, 16 Apr 2026 10:22:03 +0000

Tip: apt-get handles downloads and installs. For searching and querying metadata, use apt-cache search, apt-cache show, and apt-cache depends.

apt vs apt-get vs apt-cache vs aptitude

Debian ships four front-ends to APT and the right one depends on what you're doing. apt-get and apt-cache are the older, scriptable pair — stable command-line, stable exit codes, no progress bars. apt is the newer "user-friendly" wrapper that bundles common operations across both, with colour and progress output that's deliberately not guaranteed to stay stable. aptitude is a separate tool with its own dependency resolver and an optional curses UI.

ToolWhen to reach for it
apt-getshell scripts, CI, anything where the output is parsed or where you need stable behaviour
apt-cachequerying metadata — search, show, depends, rdepends, policy
aptinteractive use at the terminal — friendlier prompts, progress bars, colour
apt-markflipping the auto/manual/hold state on already-installed packages
aptituderesolving difficult conflicts; offers alternative dependency solutions interactively
dpkglow-level package operations (installing a .deb, querying file ownership)
bash
# apt rolls up the most common subcommands from both apt-get and apt-cache:
apt install PACKAGE      # = apt-get install PACKAGE
apt search PACKAGE       # = apt-cache search PACKAGE
apt show PACKAGE         # = apt-cache show PACKAGE + apt-cache policy PACKAGE
apt list --installed     # = dpkg -l with friendlier output
apt list --upgradable    # what would `upgrade` actually do?

Output: (none — exits 0 on success)

[!WARN] The apt CLI prints WARNING: apt does not have a stable CLI interface when used in scripts for a reason: its output format can change between releases. Use apt-get / apt-cache / dpkg for any automation.

APT 3.0 (Debian 13 trixie, Ubuntu 24.10+)

APT 3.0 — the version that ships in Debian 13 "trixie" (August 2025) and Ubuntu 24.10+ — is the first major APT release since 2.0 and changes several behaviours that affect day-to-day use. The biggest visible change is the redesigned columnar UI for the apt (not apt-get) front-end; the biggest invisible change is the new dependency solver and the switch from GnuPG to Sequoia (sqv) for signature verification.

bash
# Check which APT you have
apt --version
# apt 3.0.0 (amd64)

# Opt in to the new solver from APT 2.9.x (it's default in 3.0)
sudo apt-get install --solver 3.0 nginx

# Force the legacy solver if 3.0 picks a worse resolution
sudo apt-get install --solver internal nginx

Output (apt --version):

text
apt 3.0.0 (amd64)

What actually changed:

AreaPre-3.03.0
apt UIPlain text, single-column package listColourised, columnar — removals in red, installs/upgrades in green
Output pagingNone — long output scrolled pastapt search, apt show, apt policy auto-page to less
Dependency solverInternal (since 1.1)Solver 3.0 — backtracking, ~40 % faster, phased-update aware
Signature verificationgpgv (GnuPG)sqv (Sequoia) — stricter, no SHA-1 past 2026-02-01
Disk-space checkBest-effort, ignored /bootPer-mountpoint check, warns if /boot would fill
apt-keyDeprecated warningRemoved

apt-get keeps its stable scripting interface — none of the colour or pager changes apply, and --solver internal remains the default for apt-get unless you opt in. The new solver's --no-strict-pinning flag lets it fall back to non-candidate versions (e.g. installing one package from experimental and pulling matching deps from there too) without writing a preferences file.

If a 3.0 upgrade breaks a previously-working apt-get invocation in CI, the culprit is almost always either the stricter sqv rejecting an old SHA-1 keyring or the new solver picking a different candidate. Pin the legacy solver with -o APT::Solver=internal to isolate the change.

Searching and inspecting packages

Before installing something, query its metadata: who maintains it, what it depends on, what reverse-depends on it (so you know what would break if you removed it), and which version is actually available from your configured mirrors. apt-cache does the metadata work without ever touching the system.

bash
# Full-text search package names and descriptions
apt-cache search nginx
apt-cache search '^nginx-'           # names starting with nginx-
apt-cache search --names-only nginx  # only match names, not descriptions

# Show all metadata for a package
apt-cache show nginx
apt-cache show nginx | grep -E '^(Package|Version|Depends|Description-en):'

# What does this package depend on?
apt-cache depends nginx
apt-cache depends --recurse --no-recommends --no-suggests nginx

# What depends on this package? (reverse depends — invaluable before remove/purge)
apt-cache rdepends libssl3

# Which version would `install` pick, from which mirror?
apt-cache policy nginx

Output (apt-cache policy nginx):

text
nginx:
  Installed: 1.24.0-2ubuntu7
  Candidate: 1.24.0-2ubuntu7.1
  Version table:
     1.24.0-2ubuntu7.1 500
        500 http://archive.ubuntu.com/ubuntu noble-updates/main amd64 Packages
 *** 1.24.0-2ubuntu7 500
        500 http://archive.ubuntu.com/ubuntu noble/main amd64 Packages
        100 /var/lib/dpkg/status

The asterisk (***) marks the installed version; the priority numbers (500/100) are the pinning weights — see Pinning below.

dpkg — the layer below APT

dpkg is the lower-level tool that APT calls to actually unpack and configure .deb files. APT handles repositories, downloads, and dependency resolution; dpkg handles the file-level work. Reach for it when you need to query what's already installed, install a standalone .deb, or look up which package owns a given file.

bash
# List installed packages (with two-letter status code)
dpkg -l
dpkg -l | grep nginx

# Show files installed by a package
dpkg -L nginx

# Which package owns this file?
dpkg -S /usr/bin/curl
dpkg -S $(which jq)

# Install a local .deb (without dependency resolution)
sudo dpkg -i ./myapp.deb
# If it complains about unmet deps, let apt fix it:
sudo apt-get -f install

# Modern: let apt handle the .deb + its deps in one step (apt 1.1+)
sudo apt install ./myapp.deb

# Remove / purge by name
sudo dpkg --remove nginx
sudo dpkg --purge nginx

# Reconfigure a package (re-runs its postinst)
sudo dpkg-reconfigure tzdata
sudo dpkg-reconfigure -plow keyboard-configuration

# Dump the list of installed packages (for reinstalling on a new host)
dpkg --get-selections > installed.list
sudo dpkg --set-selections < installed.list
sudo apt-get dselect-upgrade

Output (dpkg -l | grep nginx):

text
ii  nginx           1.24.0-2  amd64  high performance web server
ii  nginx-common    1.24.0-2  all    small, powerful, scalable web/proxy server - common files
ii  nginx-core      1.24.0-2  amd64  nginx web/proxy server (standard version)

The two-letter status prefix encodes desired-state and current-state. ii is the only "everything is fine" combination; anything else (rc, iU, iF) indicates a half-removed or broken package that may need apt-get install -f or dpkg --purge.

apt-mark — hold, auto, and manual

apt-mark controls the metadata APT uses to decide which packages are upgradable, which were installed automatically, and which it must never touch. The three operations you'll actually use are hold (pin a package at its current version), auto/manual (toggle the "installed manually" flag, which controls what autoremove will sweep), and showhold / showmanual to audit the current state.

bash
# Pin a package — no upgrades, no removals
sudo apt-mark hold nginx
apt-mark showhold

# Release the hold
sudo apt-mark unhold nginx

# Mark a package as automatically installed — it becomes eligible for autoremove
sudo apt-mark auto python3-pip

# Mark as manually installed (the opposite — keep it across autoremove)
sudo apt-mark manual python3-pip

# List manually-installed packages (the "recreate this host" list)
apt-mark showmanual

# List auto-installed packages
apt-mark showauto

Output: (none — exits 0 on success)

Take a snapshot of apt-mark showmanual > manual.list periodically — that file is the recipe for rebuilding the host's installed-software set. Combine with sudo dpkg --get-selections > selections.list for a complete record.

sources.list anatomy

/etc/apt/sources.list (and any file under /etc/apt/sources.list.d/) lists the repositories APT consults. Each line has a type (deb for binary, deb-src for source), an HTTPS or HTTP URL, the distribution codename (noble, jammy, bookworm), and one or more components (main, restricted, universe, multiverse on Ubuntu; main, contrib, non-free, non-free-firmware on Debian).

text
# /etc/apt/sources.list — classic one-line format (Ubuntu)
deb http://archive.ubuntu.com/ubuntu noble main restricted universe multiverse
deb http://archive.ubuntu.com/ubuntu noble-updates main restricted universe multiverse
deb http://archive.ubuntu.com/ubuntu noble-backports main restricted universe multiverse
deb http://security.ubuntu.com/ubuntu noble-security main restricted universe multiverse

The newer deb822 format (one stanza per repository, multiple key/value lines) is now the default on Debian 12+ and Ubuntu 24.04+. It's more verbose but easier to script and easier for tools to add/remove cleanly.

text
# /etc/apt/sources.list.d/ubuntu.sources — deb822 format
Types: deb
URIs: http://archive.ubuntu.com/ubuntu
Suites: noble noble-updates noble-backports
Components: main restricted universe multiverse
Signed-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg

Drop new sources under /etc/apt/sources.list.d/<name>.list (one-line) or <name>.sources (deb822) — one file per third-party repository so they're easy to add, audit, and remove.

bash
# Show every repository that contributes packages
apt-cache policy
grep -rh ^deb /etc/apt/sources.list /etc/apt/sources.list.d/ 2>/dev/null

# Which mirror is APT using right now?
apt-config dump | grep -i mirror

Output: (apt-cache policy)

text
Package files:
 100 /var/lib/dpkg/status
     release a=now
 500 http://archive.ubuntu.com/ubuntu noble/main amd64 Packages
     release v=24.04,o=Ubuntu,a=noble,n=noble,l=Ubuntu,c=main,b=amd64
Pinned packages:

GPG keys and signed-by

APT verifies every package against a signature, and the public keys that anchor that verification live under /etc/apt/trusted.gpg.d/ (system-wide) or — for modern third-party repositories — under /etc/apt/keyrings/, scoped to a specific source via Signed-By=. The older apt-key add is deprecated and removed in Ubuntu 22.04+ / Debian 12+; never use it on a new system.

bash
# Download a vendor's signing key and dearmor it to /etc/apt/keyrings/
sudo install -d -m 0755 /etc/apt/keyrings
curl -fsSL https://example.com/keyring.asc \
  | sudo gpg --dearmor -o /etc/apt/keyrings/example.gpg

# Confirm the file is binary keyring data (not ASCII-armoured)
sudo gpg --no-default-keyring --keyring /etc/apt/keyrings/example.gpg --list-keys

# Reference the keyring from a source (one-line format)
echo "deb [signed-by=/etc/apt/keyrings/example.gpg] https://example.com/repo noble main" \
  | sudo tee /etc/apt/sources.list.d/example.list

# Or deb822:
sudo tee /etc/apt/sources.list.d/example.sources <<EOF
Types: deb
URIs: https://example.com/repo
Suites: noble
Components: main
Signed-By: /etc/apt/keyrings/example.gpg
EOF

sudo apt-get update

Output (apt-get update with a properly-signed repo):

text
Get:1 https://example.com/repo noble InRelease [3,012 B]
Hit:2 http://archive.ubuntu.com/ubuntu noble InRelease
Reading package lists... Done

If you see NO_PUBKEY or signatures couldn't be verified, the key isn't installed in the location the source references. Do not fix it by disabling signature checks — re-fetch the key and put it where Signed-By= points.

[!WARN] apt-key add is deprecated. Keys added with it are trusted globally, meaning any repository can ship a package signed with that key. Signed-By= scopes a key to a single source, which is what you want.

[!WARN] SHA-1 keys stopped working on 2026-02-01. APT (via the new sqv verifier introduced in 2.9.19 and default in 3.0) treats SHA-1 self-signatures as untrusted past that date, so older third-party keyrings now fail apt-get update with Malformed OpenPGP message or signatures couldn't be verified. The fix is to re-fetch the vendor's current key — most have re-signed with SHA-256/SHA-512 — or, in extremis, set APT::Hashes::SHA1::Weak "false"; in /etc/apt/apt.conf.d/ only as a short-term bridge.

PPAs (Ubuntu only)

A PPA — Personal Package Archive — is a Launchpad-hosted Ubuntu repository. add-apt-repository adds the source line and imports the signing key in one step; under the hood it writes to /etc/apt/sources.list.d/ and /etc/apt/trusted.gpg.d/.

bash
# Install the helper (one-time)
sudo apt-get install -y software-properties-common

# Add a PPA
sudo add-apt-repository ppa:deadsnakes/ppa
sudo apt-get update

# Add WITHOUT triggering an immediate apt-get update
sudo add-apt-repository --no-update ppa:deadsnakes/ppa

# Remove a PPA (also strips the signing key from older systems)
sudo add-apt-repository --remove ppa:deadsnakes/ppa

Output: (none — exits 0 on success)

On non-Ubuntu Debian, add-apt-repository still works for plain deb … URLs but cannot resolve the ppa: shorthand.

Pinning and preferences

By default, APT installs the highest-version candidate from any configured source. Pinning assigns priorities to sources, suites, or packages so you can prefer one repository over another or hold a single package back from a faster-moving release. Files live under /etc/apt/preferences.d/.

The priority numbers carry specific meanings:

RangeEffect
> 1000install even if it means downgrading
1000install, never downgrade
500normal: install if not yet installed, upgrade if newer
100keep installed, don't auto-install
1–99don't install unless explicitly requested
≤ 0never install
text
# /etc/apt/preferences.d/99-pin-nodejs
# Prefer the NodeSource repo over Ubuntu's stock node
Package: nodejs
Pin: origin "deb.nodesource.com"
Pin-Priority: 1001
text
# /etc/apt/preferences.d/99-hold-nginx
# Never upgrade nginx automatically
Package: nginx nginx-*
Pin: release *
Pin-Priority: -1
bash
# Verify the resolved priorities
apt-cache policy nodejs
apt-cache policy nginx

Output: (apt-cache policy nodejs)

text
nodejs:
  Installed: 20.11.1-1nodesource1
  Candidate: 20.11.1-1nodesource1
  Version table:
 *** 20.11.1-1nodesource1 1001
        500 https://deb.nodesource.com/node_20.x noble/main amd64 Packages
        100 /var/lib/dpkg/status
     12.22.9~dfsg-1ubuntu3 500
        500 http://archive.ubuntu.com/ubuntu noble/universe amd64 Packages

A more common modern alternative is apt-mark hold <pkg> — simpler, no file to manage, and apt-mark showhold shows you everything that's pinned. Reach for preferences files only when you need cross-package or origin-wide rules.

unattended-upgrades

unattended-upgrades is the official daemon for installing security updates automatically. It runs daily via a systemd timer, reads /etc/apt/apt.conf.d/50unattended-upgrades to decide which upgrades it's allowed to apply, and optionally reboots the system if a kernel or library update requires it.

bash
# Install + enable interactive setup
sudo apt-get install -y unattended-upgrades
sudo dpkg-reconfigure --priority=low unattended-upgrades

# Test what it would do without changing anything
sudo unattended-upgrade --dry-run --debug

# Trigger a run now (instead of waiting for the timer)
sudo unattended-upgrade -v

# Inspect the schedule
systemctl list-timers apt-daily.timer apt-daily-upgrade.timer

Output: (systemctl list-timers)

text
NEXT                        LEFT       LAST                        PASSED       UNIT                     ACTIVATES
Mon 2026-05-25 06:12:43 UTC 4h 18min   Sun 2026-05-24 06:11:39 UTC 19h ago      apt-daily.timer          apt-daily.service
Mon 2026-05-25 06:33:01 UTC 4h 38min   Sun 2026-05-24 06:33:55 UTC 19h ago      apt-daily-upgrade.timer  apt-daily-upgrade.service

Key config keys in /etc/apt/apt.conf.d/50unattended-upgrades:

text
Unattended-Upgrade::Allowed-Origins {
    "${distro_id}:${distro_codename}-security";
    "${distro_id}ESMApps:${distro_codename}-apps-security";
    "${distro_id}ESM:${distro_codename}-infra-security";
};
Unattended-Upgrade::Package-Blacklist { "linux-image-*"; "nginx"; };
Unattended-Upgrade::Automatic-Reboot "true";
Unattended-Upgrade::Automatic-Reboot-Time "03:00";
Unattended-Upgrade::Mail "alice@example.com";
Unattended-Upgrade::MailReport "on-change";

Logs land at /var/log/unattended-upgrades/unattended-upgrades.log and in the journal — journalctl -u unattended-upgrades.service. See systemd units for the timer that triggers it.

Useful environment overrides

APT's behaviour can be tuned with environment variables and -o overrides without editing config files. Two situations come up often: noninteractive automation (don't prompt for conffile decisions) and isolated package operations (don't trigger triggers from other packages mid-install).

bash
# Fully noninteractive: never prompt, keep old config files on conflict
sudo DEBIAN_FRONTEND=noninteractive apt-get \
  -o Dpkg::Options::="--force-confdef" \
  -o Dpkg::Options::="--force-confold" \
  -y install nginx

# Force the NEW upstream config (the opposite policy)
sudo apt-get -o Dpkg::Options::="--force-confnew" install -y openssh-server

# Use a specific cache directory
sudo apt-get -o Dir::Cache::Archives="/tmp/aptcache" install -y nginx

# Override the default timeout
sudo apt-get -o Acquire::http::Timeout=30 update

# Disable IPv6 for the next operation
sudo apt-get -o Acquire::ForceIPv4=true update

Output: (none — exits 0 on success)

DEBIAN_FRONTEND=noninteractive is the variable to set when running APT from Ansible, Dockerfiles, or any other automation that can't answer prompts.

Common pitfalls

  1. Forgot apt-get update — APT installs from its local cache, which can be stale by months on a freshly-imaged VM. Always apt-get update first, or pair it as apt-get update && apt-get install -y ….
  2. apt update in scriptsapt is not a stable CLI; warn message goes to stderr. Use apt-get for any automation.
  3. apt-key add on modern systems — deprecated in 2021, removed in 22.04/12. Use Signed-By=/etc/apt/keyrings/<repo>.gpg instead.
  4. dpkg -i without -f install — leaves the system half-broken if dependencies are missing. Always follow with sudo apt-get -f install, or use apt install ./package.deb instead.
  5. remove vs purgeremove keeps /etc/<pkg>/ configuration. If a re-install of the same package picks up stale config and behaves oddly, the answer is usually purge then reinstall.
  6. Mixing repositories from different suites — pulling unstable packages onto a stable system creates "FrankenDebian" with incompatible glibc versions. Use pinning to keep the boundary clean.
  7. autoremove after manual installs — packages installed via dpkg -i start life flagged as auto-installed. autoremove will sweep them out unless you mark them manual: sudo apt-mark manual <pkg>.
  8. Disk full during upgradedist-upgrade can need several GB of free space for downloads + new versions. Run apt-get clean first and check df -h /var/cache /var / before kicking off a big upgrade.
  9. Ignoring NEEDRESTART warnings — services using upgraded libraries keep running with the old library until they restart. needrestart (often installed by default) lists them; restart or reboot to actually apply the security fix.
  10. Held packages blocking upgradeapt-get upgrade silently keeps held packages back. apt-mark showhold reveals them; sudo apt-mark unhold <pkg> releases.

Real-world recipes

Add a third-party repository safely

The 2026-correct procedure: fetch the vendor's key, store it under /etc/apt/keyrings/, reference it via Signed-By= in a per-vendor source file. Never curl | bash an install script that you haven't read.

bash
# Example: install Docker CE from the official Docker apt repo
sudo install -d -m 0755 /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg \
  | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg

sudo tee /etc/apt/sources.list.d/docker.sources <<EOF
Types: deb
URIs: https://download.docker.com/linux/ubuntu
Suites: $(. /etc/os-release && echo "$VERSION_CODENAME")
Components: stable
Architectures: $(dpkg --print-architecture)
Signed-By: /etc/apt/keyrings/docker.gpg
EOF

sudo apt-get update
sudo apt-get install -y docker-ce docker-ce-cli containerd.io

Output: (none — exits 0 on success)

Hold a package at its current version

For software you've validated at a specific version (a database, a CI runner, a kernel module) and don't want auto-upgraded.

bash
sudo apt-mark hold postgresql-16
apt-mark showhold

# Confirm: an upgrade leaves the held package alone
sudo apt-get upgrade
# postgresql-16 is held back

Output: (apt-mark showhold)

text
postgresql-16

Audit installed packages

Generate a deterministic list of manually installed packages to feed Ansible/Terraform/Nix or to recreate a host. Excludes the long tail of auto-installed dependencies.

bash
# Sorted, stable list
apt-mark showmanual | sort > installed.list

# Diff against another host's list
diff <(ssh myhost apt-mark showmanual | sort) installed.list

# Reinstall the same set on a fresh host
xargs -a installed.list sudo apt-get install -y

Output: (none — exits 0 on success)

Recover from a broken install

When dpkg -i fails mid-install or a package's postinst script errors out, the package database can be left half-configured. The fix is a sequence of dpkg --configure -a (finish any pending configures), apt-get -f install (resolve missing deps), and apt-get update to sanity-check.

bash
sudo dpkg --configure -a
sudo apt-get -f install
sudo apt-get update
sudo apt-get upgrade

Output: (none — exits 0 on success)

If a specific package is stuck, identify it with dpkg -l | grep -v '^ii' (any status that isn't ii) and either --purge or --reinstall it.

Take a backup before a risky upgrade

For desktops and small servers, exporting the installed list + /etc/ is the cheapest rollback insurance.

bash
sudo dpkg --get-selections > ~/dpkg-selections-$(date +%F).txt
sudo tar czf ~/etc-$(date +%F).tar.gz /etc
# After a bad upgrade:
#   sudo dpkg --set-selections < ~/dpkg-selections-2026-05-24.txt
#   sudo apt-get dselect-upgrade

Output: (none — exits 0 on success)

Find a file's package and look up its docs

The lookup chain when an unfamiliar binary appears on a host: dpkg -S to identify the package, apt-cache show for its metadata and homepage, dpkg -L to see every other file it installed.

bash
$ dpkg -S $(which jq)
jq: /usr/bin/jq
$ apt-cache show jq | grep -E '^(Description-en|Homepage):'
Description-en: lightweight and flexible command-line JSON processor
Homepage: https://stedolan.github.io/jq/
$ dpkg -L jq | grep -E '(bin|man)'
/usr/bin/jq
/usr/share/man/man1/jq.1.gz

Output: (interleaved with commands above)

Bookmark apt-cache policy <pkg> — it's the single most useful command for "what version would APT install, from which mirror?". When troubleshooting "why didn't the upgrade pull X?", this is the first thing to run.

[!WARN] Never rm -rf /var/lib/dpkg/ to "fix" APT. That's the package database; deleting it makes the system unrecoverable without rebuilding from package backups. The right reset for a wedged APT is sudo dpkg --configure -a then sudo apt-get -f install.

Sources

Debian 13 trixie release notes — official changelog covering APT 3.0, the Sequoia switch, and migration guidance.

What's new in APT 3.0 (LWN) — deep dive on the new columnar UI, Solver 3.0, and the apt/apt-get split, by APT maintainer Julian Andres Klode's reviewers.

Debian APT 3.0 stable released (Phoronix) — summary of the 3.0 release covering the new solver, refined text UI, and Sequoia integration.

Ubuntu apt package changelog — authoritative per-version changelog for the apt package on Ubuntu; useful for spotting behaviour changes between LTS releases.

APT, SHA-1 keys, and the 2026-02-01 cutoff — practitioner write-up of the SHA-1 deprecation, including the sqv failure modes and short-term workarounds for unmaintained third-party repos.

Debian wiki — OpenPGP/Sequoia — background on the Sequoia-PGP migration, why gpgv was replaced with sqv, and the implications for repository signing.

Ubuntu 24.04 LTS release notes — covers the move to deb822 as the default sources format (/etc/apt/sources.list.d/ubuntu.sources) and the keyrings-directory conventions.

apt-get(8) — Debian trixie manpage — current upstream reference for every flag, including the new --solver and OpenPGP-related options.