Like most Linux package management, this is a two-layer system: a low-level tool that installs a single package file, and a higher-level tool that knows how to find packages, resolve dependencies between them, and fetch them from a repository.

dpkg: the low-level tool

dpkg installs, removes, and queries .deb package files directly, with no awareness of remote repositories and no automatic dependency resolution - if a .deb needs another package that isn't installed, dpkg will complain rather than fetch it for you.

sudo dpkg -i package.deb     # install a local .deb file
dpkg -l                       # list all installed packages
dpkg -L nginx                 # list every file nginx's package installed
dpkg -S /usr/bin/nginx        # which package owns this file?

dpkg -L and dpkg -S are especially useful for figuring out where a config file actually came from, or what else might break if you remove something.

APT: the layer you actually use day to day

APT (Advanced Package Tool) wraps dpkg, adding dependency resolution and repository management. apt is the modern, user-facing command; apt-get/apt-cache are the older, more scriptable commands it's gradually superseding for interactive use - both still work, and you'll see both in older documentation.

sudo apt update              # refresh the local package index from repositories
sudo apt upgrade             # upgrade all installed packages to latest available
sudo apt install nginx       # install a package, resolving dependencies automatically
sudo apt remove nginx        # remove the package, keep its config files
sudo apt purge nginx         # remove the package AND its config files
sudo apt autoremove          # remove packages that were installed as dependencies and are no longer needed
apt search "web server"      # search package names and descriptions
apt show nginx               # details about a specific package before installing

apt update doesn't install anything - it only refreshes APT's local knowledge of what's available. Forgetting to run it before install means you might get an outdated version, or a version that's already been pulled from the repository entirely.

Where repositories are configured

/etc/apt/sources.list
/etc/apt/sources.list.d/*.list

Each line names a repository URL, distribution codename, and component (main, universe, restricted on Ubuntu). Adding a third-party repository means adding a file here (and importing its signing key) - be selective about what you add, since every repository you trust can push arbitrary code to your system via a package update.

Holding a package at its current version

Occasionally an upgrade should be deliberately skipped (a known-broken release, a kernel version you're pinning for driver compatibility):

sudo apt-mark hold linux-image-generic
sudo apt-mark unhold linux-image-generic

Checking what's actually available

apt-cache policy nginx
nginx:
  Installed: 1.24.0-2ubuntu7
  Candidate: 1.24.0-2ubuntu7
  Version table:
 *** 1.24.0-2ubuntu7 500
        500 http://archive.ubuntu.com/ubuntu noble/main amd64 Packages
        100 /var/lib/dpkg/status

Useful for confirming which repository a version is actually coming from when multiple sources provide the same package name.

See DNF and RPM and Pacman for the equivalent stack on Fedora/RHEL and Arch, and Building From Source for what to do when a package isn't available through any of these at all.