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-getandapt-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
| Task | apt | apt-get / apt-cache | What differs |
|---|---|---|---|
| Refresh package lists | apt update | apt-get update | apt ends with “N packages can be upgraded” |
| Upgrade installed packages | apt upgrade | apt-get upgrade --with-new-pkgs | Plain apt-get upgrade never adds a package, so it keeps more back |
| Upgrade, removing if needed | apt full-upgrade | apt-get dist-upgrade | apt also removes unused old kernels |
| Install | apt install PKG | apt-get install PKG | apt 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 |
| Reinstall | apt reinstall PKG | apt-get reinstall PKG | Same (alias for install --reinstall) |
| Remove, keep config | apt remove PKG | apt-get remove PKG | Same |
| Remove with config | apt purge PKG | apt-get purge PKG | Same |
| Remove unused dependencies | apt autoremove | apt-get autoremove | Same |
| Search | apt search WORD | apt-cache search WORD | apt adds version, suite and [installed] |
| Package details | apt show PKG | apt-cache show PKG | apt shows the candidate only; apt-cache every version |
| Versions and repositories | apt policy PKG | apt-cache policy PKG | Identical output |
| Dependencies | apt depends PKG | apt-cache depends PKG | Same |
| List installed | apt list --installed | dpkg-query -W | No apt-get subcommand |
| List upgradable | apt list --upgradable | apt-get -s upgrade | Read the Inst lines |
| Edit sources | apt edit-sources | None | Opens an editor with a basic check |
| Clear the download cache | apt clean | apt-get clean | Same; autoclean only drops files that can no longer be downloaded |
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:
$ 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.0So 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:
$ 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:
| Setting | apt | apt-get | What you see |
|---|---|---|---|
Upgrade-Allow-New | On | Off | upgrade may install new packages to satisfy dependencies |
DPkg::Progress-Fancy | On | Off | A progress bar at the bottom of the terminal while dpkg runs |
APT::Color | On | Off | Coloured package names and markers |
Show-Update-Stats | On | Off | update ends with the number of upgradable packages |
DPkg::Lock::Timeout | -1 at a terminal (no limit), 120 s otherwise | Not set | apt shows Waiting for cache lock; apt-get stops at once with Could not get lock |
Keep-Downloaded-Packages | Off | On | apt deletes installed .deb files from /var/cache/apt/archives |
Cache::Show::Version | 2 | 1 (apt-cache) | show rewrites the record: adds Download-Size and APT-Sources, prints sizes in kB, leaves out Filename and checksums |
Cache::AllVersions | 0 | 1 (apt-cache) | show prints the candidate version only |
InteractiveReleaseInfoChanges | On | Off | apt can ask when a repository changes its release info; apt-get needs --allow-releaseinfo-change |
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:
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:
$ 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 programsapt-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:
$ 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 upgradenever removes a package and never installs a new one. An update that needs either is left at its current version.apt upgradenever removes a package, but will install new packages if an update depends on them. That is the same asapt-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-upgradealso removes unused old kernels;apt-get dist-upgradedoes 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:
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.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.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:
$ apt-mark showhold
demo-tool
The following packages have been kept back:
demo-toolCheck 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-getthroughout and tell you to runapt-get updateandapt-get installin the sameRUN, so a cached layer never installs from stale lists. - cloud-init: on Debian and Ubuntu it runs
apt-getwith--assume-yes --quiet,--force-confoldandDEBIAN_FRONTEND=noninteractive, and itspackage_upgrade: truerunsapt-get dist-upgrade(from cloud-init’s apt source code). - Ansible:
ansible.builtin.aptinstalls through thepython3-aptlibrary and, forupgrade:, callsapt-getoraptitude, notapt. Withapt-get,upgrade: yesrunsapt-get upgrade --with-new-pkgsandupgrade: distrunsapt-get dist-upgrade.
A shell script that installs packages without asking questions:
#!/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-pkgsThe same in a Dockerfile, cleaning the lists in the same layer:
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:
# 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
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.