apt and Repositories: The High-Level Package Layer
Package Management Basics established the two-layer model: a low-level tool installs a single file, while a high-level tool talks to repositories and resolves dependencies automatically. On the Debian/Ubuntu family, that high-level tool is apt (Advanced Package Tool). Most day-to-day administration — installing software, upgrading a system, removing something no longer needed — runs through apt.
This article covers the apt command set, the repository mechanism behind it, how metadata refresh actually works, and how to add a third-party repository without opening a security hole. The goal is not just memorizing commands, but understanding what apt is doing at each step and why skipping a step — signature verification, in particular — is never a safe shortcut.
apt, apt-get, and apt-cache: Three Names, One Job
Historically, the Debian/Ubuntu high-level layer was split across several separate commands: apt-get (install, upgrade), apt-cache (query metadata — search, view dependencies), and apt-config. Starting in 2014, Debian unified the most commonly used parts of these into a single, more user-friendly command: apt. apt adds a progress bar, colored output, and shorter syntax, but the underlying mechanism is identical — everything eventually calls dpkg.
In practice:
- for interactive, manual use at a terminal,
aptis preferred, since it is designed for a human to read; - for a script or automated pipeline,
apt-getandapt-cacheare traditionally recommended, because their output format and argument behavior are officially guaranteed to stay stable across versions —apt's output format is explicitly allowed to change at any time.
Note
This distinction is small but matters in production: if you parse apt's text output with grep in a script, a future version change could silently break that script. For automation, prefer apt-get/apt-cache, or a purpose-built machine-readable interface such as dpkg-query.
Core apt Commands
Refreshing the Metadata Index
This command installs or upgrades nothing. It only refreshes the local cache of available packages and their versions — the metadata index introduced in Package Management Basics. sudo is required because this writes to /var/lib/apt/lists/, a system-wide directory an ordinary user cannot write to.
Sample output:
Hit:1 http://archive.ubuntu.com/ubuntu noble InRelease
Get:2 http://archive.ubuntu.com/ubuntu noble-updates InRelease [126 kB]
Get:3 http://security.ubuntu.com/ubuntu noble-security InRelease [126 kB]
Fetched 252 kB in 1s (198 kB/s)
Reading package lists... Done
Hit means that source has not changed since the last refresh; Get means new metadata was actually downloaded. This command should always run before any apt install/upgrade — otherwise apt may try to install a version that no longer exists, or fail to find a package that was only just published.
Searching and Inspecting a Package
search lists every package whose name or description matches — no sudo required, since it only reads the local index. show displays full detail on one specific package: version, size, dependencies, and description — answering "exactly what is this, and how much space will it take" before you commit to installing anything.
Installing
Sample output (trimmed):
The following additional packages will be installed:
libnginx-mod-http-geoip2 nginx-common nginx-core
Suggested packages:
fcgiwrap nginx-doc
The following NEW packages will be installed:
libnginx-mod-http-geoip2 nginx nginx-common nginx-core
0 upgraded, 4 newly installed, 0 to remove and 3 not upgraded.
Need to get 1,105 kB of archives.
After this operation, 3,456 kB of additional disk space will be used.
Do you want to continue? [Y/n]
apt automatically resolved the dependency chain covered in Package Management Basics, adding the extra packages nginx actually needs. The [Y/n] prompt is a deliberate safety checkpoint: you see exactly how many packages and how much disk space are involved before confirming. In a scripted, non-interactive context, confirm automatically with:
Warning
Use -y carefully in automation — it removes the final "how many packages" review from the process. Even when -y is used in automated scripts on a production server, logging exactly which packages were installed for later review should remain standard practice.
Upgrading: update, upgrade, and full-upgrade
These three commands are frequently confused, but each does something distinct:
| Command | What it does |
|---|---|
apt update |
Refreshes the metadata index only; installs nothing |
apt upgrade |
Upgrades installed packages to newer versions, but skips any upgrade that would require adding or removing a package |
apt full-upgrade (formerly dist-upgrade) |
Same as upgrade, but also removes or adds packages if the upgrade genuinely requires it |
The standard practice is refreshing the index with update, then applying available upgrades with upgrade. full-upgrade is usually needed for larger version transitions (for example, when a kernel meta-package changes) and, because it changes more, should be reviewed before running. Planning safe upgrade windows on a production server is the focus of Production Updates.
Removing: remove vs. purge
remove deletes the executable files and libraries but leaves configuration under /etc/nginx/ in place — deliberately, so that reinstalling later restores your previous settings. purge additionally deletes the configuration files entirely. If you genuinely want the software gone without a trace, use purge.
Danger
apt purge deletes configuration files irreversibly. If /etc/nginx/nginx.conf contains manual customizations that are not backed up elsewhere, purge destroys them permanently. Back up important configuration before purging:
To clean up dependency packages that are no longer needed by anything else on the system:
Repositories: sources.list and the Modern deb822 Format
apt determines which sources to search using /etc/apt/sources.list and additional files under /etc/apt/sources.list.d/. Two formats exist.
Classic one-line format (in .list files):
debmarks a binary package source (deb-srcis for source code, rarely needed);http://archive.ubuntu.com/ubuntuis the repository address;nobleis the distribution's code name (Ubuntu 24.04 LTS);main restricted universe multiverseare components — categories reflecting licensing and support level.
Modern deb822 format (in .sources files, the default on Ubuntu 24.04 and later):
Types: deb
URIs: http://archive.ubuntu.com/ubuntu
Suites: noble noble-updates noble-security
Components: main restricted universe multiverse
Signed-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg
This format expresses multiple Suites/Components in one readable file and binds a signing key explicitly through Signed-By — directly connected to the security practice covered below. Both formats can coexist; apt reads either.
Tip
Check which format your system uses:
Ubuntu 24.04 (noble) and later ship the first file.
Adding a Third-Party Repository Safely
Sometimes the software you need (Docker, a newer Node.js) is not in the official Ubuntu repository, or is out of date there. The vendor then provides its own repository. Adding one safely involves three steps: fetch the key, register the source, refresh the index.
Step 1. Download the GPG public key into a dedicated keyring directory.
sudo install -d -m 0755 /etc/apt/keyrings
curl -fsSL https://example.com/repo/gpg-key.asc | sudo gpg --dearmor -o /etc/apt/keyrings/example.gpg
curl fetches the key; gpg --dearmor converts it from ASCII-armored text into the binary format apt expects. The result is saved under /etc/apt/keyrings/ — a directory used specifically for this purpose, kept separate from the system-wide keyring.
Danger
Older guides often recommend apt-key add — this command is officially deprecated starting with Ubuntu 22.04 and is being phased out, because it adds the key to one shared keyring trusted for every repository: if one third-party key is ever compromised, it could then sign packages claiming to be from any repository at all. The modern, safe approach binds each source to its own specific key file through Signed-By.
Step 2. Register the repository with Signed-By.
echo "deb [signed-by=/etc/apt/keyrings/example.gpg] https://example.com/repo noble stable" \
| sudo tee /etc/apt/sources.list.d/example.list
[signed-by=...] means packages from this specific repository are accepted only if signed by this exact key — the fix for the shared-keyring problem described above.
Step 3. Refresh the index and verify.
If example.list and its key are configured correctly, the new repository's address appears in a Hit or Get line, with no error.
Warning
Adding a repository from an unverified or untrusted source exposes the entire system to a supply-chain attack, because apt runs that repository's installation scripts with root privileges. Only use a source and key documented officially by the software's vendor, delivered over HTTPS.
apt-cache policy: Seeing Which Version Comes From Where
When multiple repositories offer different versions of the same package, knowing which one apt will actually choose matters:
nginx:
Installed: 1.24.0-2ubuntu7
Candidate: 1.24.0-2ubuntu7.1
Version table:
1.24.0-2ubuntu7.1 500
500 http://security.ubuntu.com/ubuntu noble-security/main amd64 Packages
1.24.0-2ubuntu7 500
500 http://archive.ubuntu.com/ubuntu noble/main amd64 Packages
Installed is the version currently on the system; Candidate is the version apt upgrade would install. The number (500) is the priority (pin) level: when the same package appears in multiple sources, apt chooses the one with the highest priority. This is especially useful for understanding which version wins between a security repository like noble-security and the main repository.
Installing or Pinning a Specific Version
The version table above is not merely informational — any version it lists can be installed explicitly by appending =<version> to the package name:
This is the standard way to hold a deployment to a known-good build, or to downgrade after a bad upgrade — the same syntax with an older version number, provided that version is still present in a configured repository (once it ages out of the index it can no longer be fetched). apt will name any dependencies it must also change to satisfy the request; review that list, because a downgrade can cascade into other packages.
To make a version preference persistent across future upgrades — rather than choosing it once by hand — set a priority in a file under /etc/apt/preferences.d/:
A priority above 1000 even permits an automatic downgrade to the pinned version; the 500-level numbers seen in apt-cache policy are the ordinary defaults this overrides. Pinning is powerful and easy to forget you configured — like hold below, it can silently keep a package back from security updates, so document any pin you create and review it periodically.
apt-mark hold: Freezing a Package's Version
Sometimes you deliberately need to keep one package at an older version — for example, while running an application whose compatibility with a newer version has not been confirmed:
After this, apt upgrade/full-upgrade will skip nginx, even if a newer version is available. Check the current holds:
Release the hold:
Warning
A held package also stops receiving security patches. Use hold only for a specific, justified reason — such as unverified compatibility with a newer version — and for as short a time as possible; otherwise the system gradually falls behind and accumulates unpatched vulnerabilities.
A Practical Scenario: Installing a New Package Safely
Problem. You need to install nginx on a new Ubuntu Server LTS machine, but want to see exactly what would happen before committing to it.
Step 1. Refresh the metadata.
Step 2. Check the package's availability and version (without installing anything).
Step 3. Simulate the installation.
--simulate (short form -s) changes nothing — it only shows what would happen if apt install nginx actually ran: which packages would be added and how much disk space would be used.
Step 4. Perform the actual installation.
Verify:
The first command confirms the package is registered at the dpkg level (ii status); the second confirms the service is actually running — service management is covered in depth in Service Management.
Conclusion. Previewing with --simulate — especially on a production server — is a low-cost, safe way to answer "exactly how many packages will this command change" before it actually runs.
Common Mistakes
Forgetting apt update and Working With a Stale Index
Attempting to install a package from a newly added repository and getting an "Unable to locate package" error usually means the repository was added but apt update was never run. Always run apt update after adding or changing any source.
NO_PUBKEY or "Signature Could Not Be Verified"
This usually means a repository was added, but its GPG key is not yet known to apt. The fix is to fetch the key from the repository owner's official documentation and bind it correctly with Signed-By — never bypass the check with --allow-unauthenticated.
Danger
Flags like apt install --allow-unauthenticated or apt-get -o Acquire::AllowInsecureRepositories=true disable signature verification entirely, opening the door to installing a package whose signature has been tampered with or replaced. Using these flags on a production server is almost never the right fix — fix the key problem instead.
Not Distinguishing remove from purge
Using purge when configuration needed to be kept is a common way to accidentally lose settings. Use remove for a temporary uninstall, purge for a complete cleanup.
Not Understanding a Version Conflict Between Repositories
When two repositories offer different versions of the same package, which one apt picks depends on priority (pin) level — it is not arbitrary. Use apt-cache policy to understand why an unexpected version was installed.
Exercises
- Run
apt updatefollowed byapt upgrade, and note how many packages could be upgraded (shown beforeupgradeprompts you). Explain, based on theupgrade/full-upgradedistinction, why some packages might be reported as "kept back." - Use
apt-cache policyto find which repository a package you already have installed came from, and its priority value. - Review the files under
/etc/apt/sources.listor/etc/apt/sources.list.d/on your system and count how many separate repository sources are configured (read-only — do not change anything).
Verification criterion: for each exercise, you should be able to clearly distinguish which command only reads state versus which one changes it, and explain the difference between Candidate and Installed in your own words.
Summary
apt is the Debian/Ubuntu family's high-level package tool: it manages repository metadata, resolves dependencies automatically, and ultimately hands the real installation work to dpkg. update refreshes metadata, upgrade/full-upgrade raise installed packages to newer versions, and install/remove/purge manage a package's full lifecycle. Repository sources live in sources.list or the modern deb822 format, and every source should be verified through a GPG signature — a non-negotiable part of security practice.
apt always relies on dpkg underneath. The next article, dpkg and Dependencies, drops down into that low-level layer directly — working with dpkg itself, reading its status database, and diagnosing a missing dependency.
References
- Debian Wiki: SourcesList: https://wiki.debian.org/SourcesList
- Debian Wiki: DebSrc4 (deb822 sources format): https://wiki.debian.org/DebSrc4
- Ubuntu Server documentation: package management: https://ubuntu.com/server/docs/package-management
- Linux man-pages:
apt(8): https://man7.org/linux/man-pages/man8/apt.8.html - Linux man-pages:
sources.list(5): https://man7.org/linux/man-pages/man5/sources.list.5.html - Linux man-pages:
apt-key(8)(deprecated status): https://man7.org/linux/man-pages/man8/apt-key.8.html - Debian Wiki: SecureApt: https://wiki.debian.org/SecureApt