apt and repositories
apt is the Debian package front-end. apt update refreshes the local index; apt upgrade installs newer versions; apt install fetches and wires up a package. Repositories are signed with GPG — installing from an unsigned repo is a security decision, not an option.
You type sudo apt install nginx and ten seconds later nginx is running. Behind that one line: your system fetched a cryptographically signed package index from a mirror, resolved a dependency graph, downloaded three .deb archives, unpacked them in the right order, ran post-install scripts, and registered every file in a local database. When something goes wrong — “package has unmet dependencies”, “repository is not signed”, “held broken packages” — you need to know which layer failed. The answer is almost always either the index or the signature.
After this lesson you can explain the difference between apt and apt-get, manage sources.list and the newer deb822 format, correctly sequence apt update before apt upgrade, add a third-party repository with its GPG key, and explain why installing from an unsigned repository is a security risk.
apt vs apt-get: same engine, different interface. apt-get has been around since 1998; apt was added in 2014 to give humans nicer output and saner defaults. Both call dpkg under the hood. For scripts, use apt-get — its output format is stable across releases. For interactive use, use apt.
# Interactive usage — apt
sudo apt update
sudo apt install nginx
sudo apt remove nginx
sudo apt autoremove # remove packages that are no longer needed
# Scripting — apt-get (stable, no colored output, no progress bars)
DEBIAN_FRONTEND=noninteractive apt-get install -y nginxThe key flag for automation is DEBIAN_FRONTEND=noninteractive — it suppresses interactive prompts (timezone, keyboard layout) that would hang a CI pipeline. -y auto-confirms the install. Without both of these in a Dockerfile, builds stall waiting for human input that will never come.
apt update and apt upgrade are two distinct operations — never confuse them. apt update downloads the package index (a list of what is available and at what version) from each configured repository. It does not install or upgrade anything. apt upgrade reads the local index and installs newer versions of already-installed packages.
# Step 1: refresh the local index
sudo apt update
# Step 2: install upgrades (uses the freshly fetched index)
sudo apt upgrade
# Common mistake: running upgrade without update first
# This installs "newer" versions according to a STALE index —
# you may miss critical security patches that were released since
# your last apt update.Running apt upgrade without a preceding apt update is one of the most common security mistakes on long-running servers. The system looks “up to date” but is actually running months-old package lists.
Repository sources live in /etc/apt/sources.list and /etc/apt/sources.list.d/. The classic one-liner format is:
deb [signed-by=/usr/share/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu jammy stableFields: deb (binary packages; deb-src for source), optional [options], URL, suite (e.g. jammy), component (e.g. main universe).
The newer deb822 format lives in /etc/apt/sources.list.d/*.sources:
# /etc/apt/sources.list.d/docker.sources
Types: deb
URIs: https://download.docker.com/linux/ubuntu
Suites: jammy
Components: stable
Signed-By: /usr/share/keyrings/docker.gpg
Enabled: yesdeb822 is more readable and easier to disable (Enabled: no) without deleting the file. New distributions (Debian 12+, Ubuntu 22.04+) prefer this format.
Repositories must be signed. Unsigned repositories are a supply-chain attack waiting to happen. apt uses GPG to verify that the package index and individual packages were signed by the repository owner. If the key is missing or the signature fails, apt update refuses to trust the index:
# Error you will see when a repo has no key:
# W: GPG error: http://... Release: The following signatures
# couldn't be verified because the public key is not available:
# NO_PUBKEY 7EA0A9C3F273FCD8
# Add a GPG key the correct modern way (never apt-key add — deprecated)
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg \
| sudo gpg --dearmor -o /usr/share/keyrings/docker.gpg
# Then reference it in the sources entry with signed-by=
# deb [signed-by=/usr/share/keyrings/docker.gpg] https://...apt-key add is deprecated because it added keys to a global trusted keyring that applied to ALL repositories, not just one. A compromised key for one repo could sign packages for any other. The modern signed-by= approach scopes a key to a single repository entry.
Useful apt-cache commands let you query the local index without installing anything. Separating “search the index” from “install” prevents the classic mistake of installing something to check if it exists.
# Search for packages matching a keyword
apt-cache search nginx
# Show version, dependencies, and description
apt-cache show nginx
# Show what is installed vs what is available
apt-cache policy nginx
# Output includes:
# Installed: 1.18.0-6ubuntu14.4
# Candidate: 1.18.0-6ubuntu14.5 ← newer version in the index
# Version table: ...
# Find which package owns a command
dpkg -S /usr/bin/nginxapt-cache policy is the fastest way to check if apt update is needed: if the candidate is newer than installed, the index has moved but you have not upgraded yet.
Add the Docker CE repository on Ubuntu 22.04 and install Docker correctly.
A fresh Ubuntu 22.04 server. The task: install Docker CE from the official Docker repository, not the older docker.io package in Ubuntu’s main repo.
# 1. Install prerequisites for HTTPS transport
sudo apt update
sudo apt install -y ca-certificates curl gnupg
# 2. Download and store Docker's GPG key
sudo install -m 0755 -d /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
# 3. Add the repository in deb822 format
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \
https://download.docker.com/linux/ubuntu \
$(. /etc/os-release && echo "$VERSION_CODENAME") stable" \
| sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
# 4. Update the index to pick up the new repo
sudo apt update
# 5. Install Docker CE (now from the official repo, not ubuntu's docker.io)
sudo apt install -y docker-ce docker-ce-cli containerd.ioKey decisions: step 2 scopes the key to Docker’s repo only via signed-by=. Step 3 uses VERSION_CODENAME instead of hardcoding jammy so it works on future Ubuntu releases. Step 4 must come before step 5 — the new repo is not in the index until apt update fetches it.
▸Common mistake
A common mistake is running sudo apt upgrade immediately after provisioning a server with a custom cloud image. Cloud images often ship with pinned package versions or hold specific packages (like the kernel) to avoid surprises. Running apt upgrade without reviewing apt-mark showhold first can upgrade a held package, which may break kernel module compatibility or trigger an unplanned reboot via needrestart. On production servers, review holds before upgrading and prefer apt-get upgrade --no-new-pkgs over apt-get dist-upgrade unless you specifically want new packages pulled in.
▸Why this works
Why does Ubuntu have main, universe, restricted, and multiverse components? Main = Canonical-maintained, security-patched. Universe = community-maintained, best-effort security. Restricted = proprietary drivers with official support. Multiverse = software restricted by patents or licenses. When you add universe, you are accepting that Canonical does not guarantee security updates for those packages. For a server running public-facing services, audit which universe packages you have installed and either accept that responsibility or find main alternatives.
You ran `sudo apt upgrade` on a production server and noticed it installed an old version of OpenSSL, missing a critical CVE patch released last week. What was the most likely cause?
apt is the human-facing front-end; apt-get is the script-stable variant — both call dpkg underneath. apt update refreshes the local package index from configured repositories; apt upgrade installs newer versions using that index — always run them in order. Repository sources live in /etc/apt/sources.list and .../sources.list.d/; the newer deb822 format is preferred on Ubuntu 22.04+. Every repository must be GPG-signed; use signed-by= to scope a key to a single repo, never apt-key add. Running apt upgrade on a stale index is the #1 way to believe you are patched when you are not.
Practice
Start at the top. Tasks go easiest → hardest: recall a fact, apply it to a case, then a senior-level stretch. Open one, attempt it, then reveal.
Something unclear?
Ask a question about this lesson. Questions are anonymous and go straight to the author to make the lesson better.