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.
# 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.
# 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.
# 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.
# 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
| Flag | Effect |
|---|---|
-y | Assume yes to all prompts |
-q | Quiet — suppress progress output |
--no-install-recommends | Skip recommended (not required) packages |
-f | Fix broken installs |
-s / --simulate | Dry-run: show what would happen |
--reinstall | Reinstall 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.
| Path | Purpose |
|---|---|
/etc/apt/sources.list | Classic one-line repository list (legacy on Ubuntu 24.04+, optional everywhere) |
/etc/apt/sources.list.d/*.list | Additional one-line repository files (one per third-party source) |
/etc/apt/sources.list.d/*.sources | deb822-format repository stanzas — preferred on Debian 12+ and Ubuntu 24.04+ |
/etc/apt/apt.conf | Top-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/preferences | Top-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/status | dpkg's installed-package database — never edit by hand |
# 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):
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
# 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.
# 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):
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):
bat
curl
git
nginx
ripgrep
vim
Output (dpkg -l PACKAGE — e.g. dpkg -l nginx):
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.
apt-get changelog firefox
Output:
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-gethandles downloads and installs. For searching and querying metadata, useapt-cache search,apt-cache show, andapt-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.
| Tool | When to reach for it |
|---|---|
apt-get | shell scripts, CI, anything where the output is parsed or where you need stable behaviour |
apt-cache | querying metadata — search, show, depends, rdepends, policy |
apt | interactive use at the terminal — friendlier prompts, progress bars, colour |
apt-mark | flipping the auto/manual/hold state on already-installed packages |
aptitude | resolving difficult conflicts; offers alternative dependency solutions interactively |
dpkg | low-level package operations (installing a .deb, querying file ownership) |
# 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
aptCLI printsWARNING: apt does not have a stable CLI interfacewhen used in scripts for a reason: its output format can change between releases. Useapt-get/apt-cache/dpkgfor 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.
# 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):
apt 3.0.0 (amd64)
What actually changed:
| Area | Pre-3.0 | 3.0 |
|---|---|---|
apt UI | Plain text, single-column package list | Colourised, columnar — removals in red, installs/upgrades in green |
| Output paging | None — long output scrolled past | apt search, apt show, apt policy auto-page to less |
| Dependency solver | Internal (since 1.1) | Solver 3.0 — backtracking, ~40 % faster, phased-update aware |
| Signature verification | gpgv (GnuPG) | sqv (Sequoia) — stricter, no SHA-1 past 2026-02-01 |
| Disk-space check | Best-effort, ignored /boot | Per-mountpoint check, warns if /boot would fill |
apt-key | Deprecated warning | Removed |
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-getinvocation in CI, the culprit is almost always either the strictersqvrejecting an old SHA-1 keyring or the new solver picking a different candidate. Pin the legacy solver with-o APT::Solver=internalto 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.
# 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):
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.
# 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):
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.
# 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.listperiodically — that file is the recipe for rebuilding the host's installed-software set. Combine withsudo dpkg --get-selections > selections.listfor 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).
# /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.
# /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.
# 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)
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.
# 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):
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 addis 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
sqvverifier 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 failapt-get updatewithMalformed OpenPGP messageorsignatures 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, setAPT::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/.
# 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:
| Range | Effect |
|---|---|
> 1000 | install even if it means downgrading |
1000 | install, never downgrade |
500 | normal: install if not yet installed, upgrade if newer |
100 | keep installed, don't auto-install |
1–99 | don't install unless explicitly requested |
≤ 0 | never install |
# /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
# /etc/apt/preferences.d/99-hold-nginx
# Never upgrade nginx automatically
Package: nginx nginx-*
Pin: release *
Pin-Priority: -1
# Verify the resolved priorities
apt-cache policy nodejs
apt-cache policy nginx
Output: (apt-cache policy nodejs)
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.
# 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)
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:
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).
# 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
- Forgot
apt-get update— APT installs from its local cache, which can be stale by months on a freshly-imaged VM. Alwaysapt-get updatefirst, or pair it asapt-get update && apt-get install -y …. apt updatein scripts —aptis not a stable CLI; warn message goes to stderr. Useapt-getfor any automation.apt-key addon modern systems — deprecated in 2021, removed in 22.04/12. UseSigned-By=/etc/apt/keyrings/<repo>.gpginstead.dpkg -iwithout-f install— leaves the system half-broken if dependencies are missing. Always follow withsudo apt-get -f install, or useapt install ./package.debinstead.removevspurge—removekeeps/etc/<pkg>/configuration. If a re-install of the same package picks up stale config and behaves oddly, the answer is usuallypurgethen reinstall.- Mixing repositories from different suites — pulling
unstablepackages onto astablesystem creates "FrankenDebian" with incompatible glibc versions. Use pinning to keep the boundary clean. autoremoveafter manual installs — packages installed viadpkg -istart life flagged as auto-installed.autoremovewill sweep them out unless you mark them manual:sudo apt-mark manual <pkg>.- Disk full during upgrade —
dist-upgradecan need several GB of free space for downloads + new versions. Runapt-get cleanfirst and checkdf -h /var/cache /var /before kicking off a big upgrade. - Ignoring
NEEDRESTARTwarnings — 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. - Held packages blocking upgrade —
apt-get upgradesilently keeps held packages back.apt-mark showholdreveals 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.
# 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.
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)
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.
# 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.
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.
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.
$ 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 issudo dpkg --configure -athensudo 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.