Comparisons

apt vs apt-get: what actually differs, and which one to use

apt and apt-get share one library. What differs: apt upgrade adds new dependencies, apt waits for the lock, and only apt-get is stable in scripts. Tested.

Published ~10 min read
On this page

Short answer

apt and apt-get come from the same package and drive the same library, so they install exactly the same software. apt only switches on friendlier defaults: a progress bar, colour, an upgrade count, and an upgrade that may install new dependencies. Its output can change between versions, so keep apt-get for anything automated.
  • Typing at a terminal: sudo apt update, sudo apt upgrade, sudo apt install PKG.
  • Scripts, Dockerfiles, cron jobs, cloud-init: apt-get and apt-cache.

Everything below with output was run on one of our own Debian 12 servers with apt 2.6.1: read-only commands as an unprivileged user, and the upgrade examples in an isolated APT root with a small test repository, so the real system was never touched. Version numbers for Debian 13 and Ubuntu 24.04 come from their package archives.

apt and apt-get commands side by side

Taskaptapt-get / apt-cacheWhat differs
Refresh package listsapt updateapt-get updateapt ends with “N packages can be upgraded”
Upgrade installed packagesapt upgradeapt-get upgrade --with-new-pkgsPlain apt-get upgrade never adds a package, so it keeps more back
Upgrade, removing if neededapt full-upgradeapt-get dist-upgradeapt also removes unused old kernels
Installapt install PKGapt-get install PKGapt waits for the lock (at a terminal until it is free, in a script up to 120 s) and deletes the downloaded .deb after installing
Reinstallapt reinstall PKGapt-get reinstall PKGSame (alias for install --reinstall)
Remove, keep configapt remove PKGapt-get remove PKGSame
Remove with configapt purge PKGapt-get purge PKGSame
Remove unused dependenciesapt autoremoveapt-get autoremoveSame
Searchapt search WORDapt-cache search WORDapt adds version, suite and [installed]
Package detailsapt show PKGapt-cache show PKGapt shows the candidate only; apt-cache every version
Versions and repositoriesapt policy PKGapt-cache policy PKGIdentical output
Dependenciesapt depends PKGapt-cache depends PKGSame
List installedapt list --installeddpkg-query -WNo apt-get subcommand
List upgradableapt list --upgradableapt-get -s upgradeRead the Inst lines
Edit sourcesapt edit-sourcesNoneOpens an editor with a basic check
Clear the download cacheapt cleanapt-get cleanSame; autoclean only drops files that can no longer be downloaded
Subcommands from man apt(8), apt-get(8) and apt-cache(8) of apt 2.6.1 (Debian 12); the output differences were checked on that version on 2 Nov 2026.

dist-upgrade and full-upgrade are the same operation under two names. apt accepts both, so old instructions with apt dist-upgrade still work. Most other subcommands (download, changelog, source, build-dep, satisfy) exist in both tools with the same meaning. The reverse does not hold: apt-get check, apt-get dselect-upgrade and apt-get indextargets have no apt equivalent (apt 2.6.1 answers E: Invalid operation), so not every apt-get line converts to apt.

What apt and apt-get actually are

APT is Debian’s package manager: it works out dependencies, downloads .deb files and hands them to dpkg, which does the actual unpacking. For most of APT’s history you used it through two programs, apt-get for changing things and apt-cache for looking things up. APT 1.0, released on 1 April 2014, added a third front end called simply apt that combines the everyday parts of both into one command.

They are not separate package managers. All three binaries ship in the same apt package and link the same two libraries:

Debian 12 · output, load addresses trimmed
$ dpkg -S /usr/bin/apt /usr/bin/apt-get /usr/bin/apt-cache
apt: /usr/bin/apt
apt: /usr/bin/apt-get
apt: /usr/bin/apt-cache

$ ldd /usr/bin/apt /usr/bin/apt-get | grep apt
/usr/bin/apt:
	libapt-private.so.0.0 => /lib/x86_64-linux-gnu/libapt-private.so.0.0
	libapt-pkg.so.6.0 => /lib/x86_64-linux-gnu/libapt-pkg.so.6.0
/usr/bin/apt-get:
	libapt-private.so.0.0 => /lib/x86_64-linux-gnu/libapt-private.so.0.0
	libapt-pkg.so.6.0 => /lib/x86_64-linux-gnu/libapt-pkg.so.6.0

So both read the same sources.list, the same package lists under /var/lib/apt/lists, the same pins and holds, and both exit with 100 on an error such as E: Unable to locate package. Whatever one of them installs, the other sees as installed.

apt versions by release

Debian 12 (bookworm)
apt 2.6.1tested
Debian 13 (trixie)
apt 3.0.3packages.debian.org
Ubuntu 24.04 LTS (noble)
apt 2.7.14 at release, 2.8.3 in noble-updatespackages.ubuntu.com
apt command added
APT 1.0, 1 April 2014Debian apt changelog
Exit code on error
100both tools, tested
Package that ships both
aptdpkg -S

What apt changes: the defaults, measured

The apt man page says it plainly: every feature of apt is also available in apt-get and apt-cache, and apt “just changes the default value of some options”. Those defaults live in a configuration scope called Binary::apt, and you can print them yourself:

Debian 12 · apt 2.6.1 · output in a script (at a terminal the lock timeout is -1, which this grep skips)
$ apt-config dump | grep '^Binary::apt::.*"[0-9]'
Binary::apt::APT::Color "1";
Binary::apt::APT::Cache::Show::Version "2";
Binary::apt::APT::Cache::AllVersions "0";
Binary::apt::APT::Cache::ShowVirtuals "1";
Binary::apt::APT::Cache::Search::Version "2";
Binary::apt::APT::Cache::ShowDependencyType "1";
Binary::apt::APT::Cache::ShowVersion "1";
Binary::apt::APT::Get::Upgrade-Allow-New "1";
Binary::apt::APT::Get::Update::InteractiveReleaseInfoChanges "1";
Binary::apt::APT::Cmd::Show-Update-Stats "1";
Binary::apt::APT::Cmd::Pattern-Only "1";
Binary::apt::APT::Keep-Downloaded-Packages "0";
Binary::apt::DPkg::Progress-Fancy "1";
Binary::apt::DPkg::Lock::Timeout "120";

That list covers the option defaults. Three more differences are built into apt itself: the old-kernel cleanup on full-upgrade, the script warning and the apt-only list and edit-sources commands. The settings you will actually notice:

Settingaptapt-getWhat you see
Upgrade-Allow-NewOnOffupgrade may install new packages to satisfy dependencies
DPkg::Progress-FancyOnOffA progress bar at the bottom of the terminal while dpkg runs
APT::ColorOnOffColoured package names and markers
Show-Update-StatsOnOffupdate ends with the number of upgradable packages
DPkg::Lock::Timeout-1 at a terminal (no limit), 120 s otherwiseNot setapt shows Waiting for cache lock; apt-get stops at once with Could not get lock
Keep-Downloaded-PackagesOffOnapt deletes installed .deb files from /var/cache/apt/archives
Cache::Show::Version21 (apt-cache)show rewrites the record: adds Download-Size and APT-Sources, prints sizes in kB, leaves out Filename and checksums
Cache::AllVersions01 (apt-cache)show prints the candidate version only
InteractiveReleaseInfoChangesOnOffapt can ask when a repository changes its release info; apt-get needs --allow-releaseinfo-change
Values from apt-config dump on apt 2.6.1; apt-get and apt-cache behaviour from man apt-get(8), man apt-cache(8) and the apt NEWS file (Keep-Downloaded-Packages since APT 1.2, lock wait since 1.9.11; the terminal check is in apt-private/private-cmndline.cc).

Because these are plain options, you can give apt-get any of them on the command line. apt-get -o DPkg::Lock::Timeout=120 install PKG waits up to 120 seconds for the lock, as apt does when no terminal is attached, which is the clean answer to scripts that collide with the nightly unattended-upgrades run. If you already hit that error, the fix is in Could not get lock /var/lib/dpkg/lock-frontend.

Output differences you will notice

apt update vs apt-get update

sudo apt update and sudo apt-get update download exactly the same package lists. The only visible difference is the last line, which apt adds after building the dependency tree:

Output · apt update (test repository)
Reading package lists...
Building dependency tree...
3 packages can be upgraded. Run 'apt list --upgradable' to see them.

apt-get update stops at Reading package lists.... Neither command upgrades anything: update only refreshes the lists, and you run upgrade or full-upgrade next. Skipping it is the most common cause of E: Unable to locate package on a fresh server.

search and show

Searching for the same package with both tools:

Debian 12 · output
$ apt search --names-only '^apt-utils$'
Sorting...
Full Text Search...
apt-utils/oldstable,now 2.6.1 amd64 [installed]
  package management related utility programs

$ apt-cache search --names-only '^apt-utils$'
apt-utils - package management related utility programs

apt-cache search prints one line per hit, which is what you want to pipe into grep or awk. apt show differs in a way that is easy to miss, and two of the settings above are the cause. Cache::AllVersions set to 0 makes it show only the version that would be installed, and Cache::Show::Version set to 2 rewrites the record: it adds Download-Size and APT-Sources and prints sizes as 10.2 kB. apt-cache show prints the raw record of every version it knows, including the installed one, with Filename and checksums. When you need to see why an old version is still around, use apt-cache show or apt policy.

WARNING: apt does not have a stable CLI interface

As soon as apt’s output goes anywhere other than a terminal, it prints a warning to stderr:

Debian 12 · output
$ apt list --installed | head -n 3

WARNING: apt does not have a stable CLI interface. Use with caution in scripts.

Listing...
adduser/oldstable,now 3.134 all [installed]
adwaita-icon-theme/oldstable,now 43-1 all [installed,automatic]

The warning is not a bug and not a sign that something failed: the exit code is still 0. It goes to stderr, so 2>/dev/null hides it, and -o Apt::Cmd::Disable-Script-Warning=true switches it off. Both only silence the message. The reason behind it stays: the apt man page says apt “may change behavior between versions” when that helps interactive use, while apt-get and apt-cache keep backward compatibility “as much as possible”. Debian 13 shows why that matters: it ships apt 3.0, which regrouped apt’s output into new sections with columns and colour. A script that parsed apt 2.x output needs checking after that upgrade; one that uses apt-get or dpkg-query does not.

apt upgrade vs apt-get upgrade vs full-upgrade

This is the one difference that changes what ends up on your server. The rules, from the man pages:

  • apt-get upgrade never removes a package and never installs a new one. An update that needs either is left at its current version.
  • apt upgrade never removes a package, but will install new packages if an update depends on them. That is the same as apt-get upgrade --with-new-pkgs.
  • apt full-upgrade = apt-get dist-upgrade: installs and removes whatever it takes to bring everything up to date. Since APT 2.1.16, apt full-upgrade also removes unused old kernels; apt-get dist-upgrade does not.

To see it happen, we built a small test repository with three updates: one plain update (demo-tool 1.0 to 1.1), one whose new version depends on a library that is not installed yet (demo-app 2.0 needs libdemo2), and one whose new version conflicts with an installed package (demo-server 2.0 conflicts with demo-legacy). Then we ran each command with -s, which simulates without changing anything:

Output · apt-get -s upgrade
The following packages have been kept back:
  demo-app demo-server
The following packages will be upgraded:
  demo-tool
1 upgraded, 0 newly installed, 0 to remove and 2 not upgraded.
Output · apt -s upgrade
The following NEW packages will be installed:
  libdemo2
The following packages have been kept back:
  demo-server
The following packages will be upgraded:
  demo-app demo-tool
2 upgraded, 1 newly installed, 0 to remove and 1 not upgraded.
Output · apt-get -s dist-upgrade (apt -s full-upgrade prints the same)
The following packages will be REMOVED:
  demo-legacy
The following NEW packages will be installed:
  libdemo2
The following packages will be upgraded:
  demo-app demo-server demo-tool
3 upgraded, 1 newly installed, 1 to remove and 0 not upgraded.

apt-get -s --with-new-pkgs upgrade printed exactly the same plan as apt -s upgrade. So the classic advice “packages kept back? run dist-upgrade” is only half the story with apt: apt upgrade already handles new dependencies, and what it still keeps back is an update that would remove something. Read the REMOVED list before you confirm a full-upgrade.

Still kept back after full-upgrade?

Then something other than dependencies is stopping it. In the same test we put demo-tool on hold. It moved into the same “kept back” list, and even full-upgrade left it there:

Output excerpt · apt-mark showhold, then apt -s full-upgrade
$ apt-mark showhold
demo-tool

The following packages have been kept back:
  demo-tool

Check apt-mark showhold first and release a hold with sudo apt-mark unhold PKG only once you know why it was set. On Ubuntu the other usual cause is a phased update: Ubuntu rolls some updates out to a growing percentage of machines, and APT pins a version this machine is not yet eligible for so that it stays back (man apt_preferences, “Phased Updates”). Waiting a few days is the intended fix.

apt or apt-get in scripts, Dockerfiles, cloud-init and Ansible

Use apt-get. The tools that run apt for you already do:

  • Dockerfiles: Docker’s build best practices use apt-get throughout and tell you to run apt-get update and apt-get install in the same RUN, so a cached layer never installs from stale lists.
  • cloud-init: on Debian and Ubuntu it runs apt-get with --assume-yes --quiet, --force-confold and DEBIAN_FRONTEND=noninteractive, and its package_upgrade: true runs apt-get dist-upgrade (from cloud-init’s apt source code).
  • Ansible: ansible.builtin.apt installs through the python3-apt library and, for upgrade:, calls apt-get or aptitude, not apt. With apt-get, upgrade: yes runs apt-get upgrade --with-new-pkgs and upgrade: dist runs apt-get dist-upgrade.

A shell script that installs packages without asking questions:

install.sh · Debian, Ubuntu
#!/bin/sh
set -eu
export DEBIAN_FRONTEND=noninteractive

apt-get update
apt-get -y -o DPkg::Lock::Timeout=120 install --no-install-recommends nginx curl
apt-get -y -o DPkg::Lock::Timeout=120 upgrade --with-new-pkgs

The same in a Dockerfile, cleaning the lists in the same layer:

Dockerfile · Debian or Ubuntu base
RUN apt-get update \
 && apt-get install -y --no-install-recommends curl ca-certificates \
 && rm -rf /var/lib/apt/lists/*

-y answers yes, but still aborts on dangerous steps such as removing an essential package. Avoid -qq on real installs: the man page notes it implies -y and should only be combined with a no-action option such as -s. If the build log says apt-get: command not found, the base image is not Debian or Ubuntu at all: apt-get: command not found has the equivalent line for Alpine, Rocky and the others.

Script-safe replacements for apt list

apt list has no apt-get subcommand, which is why it ends up in scripts and triggers the warning. These read the same data with stable output:

Debian, Ubuntu
# installed packages and versions
dpkg-query -W -f='${Package}\t${Version}\n'

# packages you installed on purpose (not pulled in as dependencies)
apt-mark showmanual

# pending upgrades: one "Inst" line per package
apt-get -s upgrade | awk '/^Inst/ {print $2}'

Each Inst line has the form Inst demo-tool [1.0] (1.1 ...): package, installed version in brackets, new version in parentheses. Use apt-get -s dist-upgrade instead if you want to count updates that would remove something.

Pros and cons

apt: strengths

  • One command for install, search, show and list
  • upgrade adds new dependencies instead of keeping updates back
  • Waits for the dpkg lock instead of failing
  • Progress bar, colour and an upgrade count

apt: trade-offs

  • Output and behaviour may change between releases (apt 3.0 did)
  • Warns on stderr in pipes, CI logs and cron mail
  • Deletes downloaded .deb files after installing

apt-get: strengths

  • Output kept stable for scripts and parsers
  • Used by Docker's examples, cloud-init and Ansible
  • Its full option list is in man apt-get

apt-get: trade-offs

  • upgrade keeps back updates that need a new package
  • Fails at once when another process holds the lock
  • Searching and listing need apt-cache or dpkg-query

Is apt-get deprecated?

No. Debian 13 still ships apt-get in the same apt package, and the Debian 13 apt man page still tells you to prefer apt-get and apt-cache in scripts. Nothing in the apt documentation announces a removal. Newer releases add features to apt first, but apt-get is the interface that is promised to stay put.

Which one should you use?

Which one should you pick?

  • Pick apt if you are typing commands yourself on a server or desktopInteractive
  • Pick apt-get if the command runs in a script, Dockerfile, CI job, cron job or provisioning toolAutomation
  • Pick apt-cache or dpkg-query if you need to parse search results or the installed package list
Following a tutorial that says apt-get? Type apt instead if you like; the result is the same package on disk.

Want a clean box to try the commands above? A Standard KVM server gives you full root on Debian 13, 12 or 11 or Ubuntu 24.04, 22.04 or 20.04, and you can reinstall from the panel whenever an experiment goes wrong. If an install was cut off halfway, start with dpkg was interrupted.

How we tested

OS image
Debian 12 (bookworm)
Versions
apt 2.6.1apt-get 2.6.1dpkg 1.21.23
Tested on
Where
One of our own Debian 12 servers
Method
Read-only commands (list, search, show, policy, apt-config dump) as an unprivileged user. The upgrade, kept-back and hold examples ran with -s (simulation) in an isolated APT root in a scratch folder, with a local test repository of synthetic demo packages; the server's own package state was not read or changed.
Not tested here
Debian 13 (apt 3.0.3) and Ubuntu 24.04 (apt 2.7.14, 2.8.3 in updates): versions from packages.debian.org and packages.ubuntu.com, behaviour from their man pages

Frequently asked

Should I use apt or apt-get?

Use apt when you type commands yourself: it shows a progress bar and colour, reports how many packages can be upgraded and waits for the package lock. Use apt-get (and apt-cache) in scripts, Dockerfiles, cron jobs and provisioning tools, because its output and behaviour are kept stable between releases while apt's may change. Both install exactly the same packages.

Is sudo apt update the same as sudo apt-get update?

Yes, both download the same package lists from the same sources. The only visible difference is that apt update ends with a line such as '3 packages can be upgraded. Run apt list --upgradable to see them.' Neither command upgrades anything; run apt upgrade or apt full-upgrade afterwards.

What is the difference between apt upgrade and apt full-upgrade?

apt upgrade never removes a package but may install new ones that an update depends on. apt full-upgrade, the same as apt-get dist-upgrade, also removes packages when an update requires it, and on apt 2.1.16 and later it removes unused old kernels too. Plain apt-get upgrade is stricter than both: it neither installs nor removes, so it keeps more updates back.

Is apt-get deprecated?

No. Debian 13 and Ubuntu 24.04 still ship apt-get in the same apt package as apt, and the apt man page tells you to prefer apt-get and apt-cache in scripts because they keep backward compatibility. Docker's examples, cloud-init and Ansible's apt module all call apt-get.

Why does apt say it does not have a stable CLI interface?

apt prints 'WARNING: apt does not have a stable CLI interface. Use with caution in scripts.' to stderr whenever its output is not a terminal, for example in a pipe or a CI log. The command still succeeds. It means apt's output format may change between versions, as it did in apt 3.0 on Debian 13, so scripts should use apt-get, apt-cache or dpkg-query instead.

Related articles

All comparisons