Linux Administration
Managing Software Repositories
In this lesson
A software repository is a structured, signed collection of packages hosted on a server that a package manager is configured to trust. Repositories are the backbone of Linux software distribution — they provide verified, pre-built binaries, handle dependency resolution, and deliver security updates automatically. Managing repositories means knowing how to add trusted sources, remove untrusted ones, verify package signatures, and keep a system's software supply chain secure.
How Repositories Work
Every repository follows the same three-component model regardless of distribution family. Understanding this model makes adding, troubleshooting, and auditing repositories straightforward — the tools differ between Debian and RHEL, but the underlying mechanism is identical.
Fig 1 — The three-component repository model: remote server, package manager, and local system
Analogy: A software repository is like a verified supplier catalogue. Your package manager is the purchasing department — it only orders from suppliers on the approved list, checks that every delivery matches the signed invoice (GPG verification), and keeps a local record of everything it has ordered. Adding a repository is like adding a new supplier to the approved list.
apt Repositories on Debian and Ubuntu
On Debian and Ubuntu systems, repository sources are defined in two places: the legacy /etc/apt/sources.list file and the preferred modern location /etc/apt/sources.list.d/ directory — one file per repository. The modern DEB822 format used in .sources files is cleaner and more explicit than the older one-liner format.
Legacy one-liner format
Used in /etc/apt/sources.list and older .list files. Still works everywhere.
deb [arch=amd64 signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu noble stable
Modern DEB822 format
Used in .sources files. Each field is explicit, easier to read and manage programmatically.
Types: deb
URIs: https://download.docker.com/linux/ubuntu
Suites: noble
Components: stable
Architectures: amd64
Signed-By: /etc/apt/keyrings/docker.gpg
# View all configured apt sources
cat /etc/apt/sources.list
ls -la /etc/apt/sources.list.d/
# View the Ubuntu/Debian default sources
grep -v "^#" /etc/apt/sources.list | grep -v "^$"
# Add a third-party repository the safe modern way (Docker as example)
# Step 1: Create the keyrings directory
sudo mkdir -p /etc/apt/keyrings
# Step 2: Download and store the GPG key
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
# Step 3: Add the repository source file
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \
https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | \
sudo tee /etc/apt/sources.list.d/docker.list
# Step 4: Update and install
sudo apt update
sudo apt install docker-ce -y
# Remove a repository
sudo rm /etc/apt/sources.list.d/docker.list
sudo apt update# ls -la /etc/apt/sources.list.d/ total 24 drwxr-xr-x 2 root root 4096 Mar 12 09:15 . drwxr-xr-x 8 root root 4096 Mar 12 09:15 .. -rw-r--r-- 1 root root 198 Mar 12 09:15 docker.list -rw-r--r-- 1 root root 187 Feb 14 11:32 nginx.list -rw-r--r-- 1 root root 142 Jan 08 14:20 pgdg.list # cat /etc/apt/sources.list.d/docker.list deb [arch=amd64 signed-by=/etc/apt/keyrings/docker.gpg] \ https://download.docker.com/linux/ubuntu noble stable
What just happened? Each third-party repository has its own dedicated .list file under sources.list.d/ — docker, nginx, and PostgreSQL are all independent. This design makes it trivial to add or remove a single repository without touching others. The signed-by= field ties this repository to its specific GPG key, so packages from this source are only accepted if signed by Docker's key — even if another repository's key is compromised.
dnf Repositories on RHEL and Rocky Linux
On RHEL-family systems, repository configuration files live in /etc/yum.repos.d/ with a .repo extension. Each file can define one or more repositories using an INI-style format. The dnf config-manager tool can add, enable, and disable repositories without manually editing files.
# /etc/yum.repos.d/myapp.repo — a typical .repo file structure
[myapp-stable]
name=MyApp Stable Repository
baseurl=https://repo.myapp.example.com/stable/el9/$basearch/
enabled=1
gpgcheck=1
gpgkey=https://repo.myapp.example.com/RPM-GPG-KEY-myapp
repo_gpgcheck=1
metadata_expire=86400
[myapp-testing]
name=MyApp Testing Repository
baseurl=https://repo.myapp.example.com/testing/el9/$basearch/
enabled=0
gpgcheck=1
gpgkey=https://repo.myapp.example.com/RPM-GPG-KEY-myapp# List all configured repositories
dnf repolist all
# List only enabled repositories
dnf repolist
# Show detailed info about a specific repo
dnf repoinfo baseos
# Enable or disable a repository temporarily (for one command)
dnf install --enablerepo=epel-testing mypackage
dnf install --disablerepo=appstream mypackage
# Enable or disable a repository permanently
sudo dnf config-manager --enable epel
sudo dnf config-manager --disable myapp-testing
# Add a repository from a URL (downloads the .repo file automatically)
sudo dnf config-manager --add-repo https://repo.example.com/example.repo
# Add EPEL (Extra Packages for Enterprise Linux) — the most common third-party repo
sudo dnf install epel-release -y
sudo dnf repolist | grep epel# dnf repolist all repo id repo name status appstream Rocky Linux 9 - AppStream enabled baseos Rocky Linux 9 - BaseOS enabled epel Extra Packages for Enterprise Linux enabled epel-testing Extra Packages for Enterprise Linux disabled extras Rocky Linux 9 - Extras enabled myapp-stable MyApp Stable Repository enabled myapp-testing MyApp Testing Repository disabled # dnf repoinfo baseos Repo-id : baseos Repo-name : Rocky Linux 9 - BaseOS Repo-status : enabled Repo-revision : 9.3 Repo-updated : Mon Mar 11 2025 Repo-pkgs : 2,458 Repo-size : 4.3 G Repo-baseurl : https://dl.rockylinux.org/pub/rocky/9/BaseOS/x86_64/os/
What just happened? dnf repolist all showed both enabled and disabled repositories — the testing repo is disabled by default, which is correct. dnf repoinfo revealed that baseos contains 2,458 packages totalling 4.3GB. The $basearch variable in repo URLs is automatically expanded to the system's architecture (x86_64, aarch64) — never hardcode the architecture in a repo file.
GPG Key Management
GPG (GNU Privacy Guard) signatures are the security mechanism that prevents a compromised or malicious repository from delivering tampered packages. Every package in a trusted repository is signed by the repository's private key. Your package manager verifies each package against the corresponding public key stored in its keyring. If the signature does not match — the download fails.
Step 1 — Obtain the repository's public GPG key
Download from the official repository URL over HTTPS. Never import a key from an unofficial source or unverified mirror.
curl -fsSL https://repo.example.com/GPG-KEY | sudo gpg --dearmor -o /etc/apt/keyrings/example.gpg
Step 2 — Verify the key fingerprint out-of-band
Before trusting the key, verify the fingerprint against the vendor's official documentation, security page, or a separate trusted source. A compromised download server could serve a malicious key over HTTPS.
gpg --show-keys /etc/apt/keyrings/example.gpg
Step 3 — Reference the key in the repository source entry
Use signed-by= (apt) or gpgkey= (dnf) to bind this key to this specific repository — so only packages from that source are verified against this key.
deb [signed-by=/etc/apt/keyrings/example.gpg] https://repo.example.com stable main
Step 4 — Audit keys periodically
Review stored keys quarterly. Remove keys for repositories you no longer use. Rotating compromised keys requires removing the old key file and replacing it with the new one from the vendor.
ls -la /etc/apt/keyrings/
# Remove: sudo rm /etc/apt/keyrings/old-repo.gpg
# View all keys stored in the apt keyring directory
ls -la /etc/apt/keyrings/
# Inspect a specific key file — shows fingerprint, UID, expiry
gpg --show-keys /etc/apt/keyrings/docker.gpg
# View keys in the legacy apt trusted keyring (pre-Ubuntu 22.04 style)
sudo apt-key list # deprecated but still shows legacy keys
# Import a key for RHEL/Rocky (RPM format)
sudo rpm --import https://repo.example.com/RPM-GPG-KEY-example
# List all imported RPM keys
rpm -q gpg-pubkey --qf '%{NAME}-%{VERSION}-%{RELEASE}\t%{SUMMARY}\n'
# Remove an RPM key
rpm -q gpg-pubkey --qf '%{NAME}-%{VERSION}-%{RELEASE}\n' | grep old-vendor
sudo rpm -e gpg-pubkey-XXXXXXXX-XXXXXXXX# gpg --show-keys /etc/apt/keyrings/docker.gpg
pub rsa4096 2017-02-22 [SCEA]
9DC858229FC7DD38854AE2D88D81803C0EBFCD88
uid Docker Release (CE deb)
# rpm -q gpg-pubkey --qf '%{NAME}-%{VERSION}\t%{SUMMARY}\n'
gpg-pubkey-fd431d51 gpg(Red Hat, Inc. (release key 2) )
gpg-pubkey-d4082792 gpg(Red Hat, Inc. (auxiliary key 3) )
gpg-pubkey-6d745a60 gpg(Rocky Enterprise Software Foundation - Release key 2022)
What just happened? gpg --show-keys revealed the full GPG fingerprint for Docker's signing key: 9DC858229FC7DD38854AE2D88D81803C0EBFCD88. This fingerprint can be verified against Docker's official documentation. If it does not match, the downloaded key is not from Docker and the repository must not be trusted.
PPAs on Ubuntu and EPEL on RHEL
Beyond official distribution repositories and first-party vendor repos, two community repository systems are widely used: PPAs (Personal Package Archives) on Ubuntu via Launchpad, and EPEL (Extra Packages for Enterprise Linux) on RHEL-family systems. Both extend the distribution's official package set with software that is not included by default.
Hosted on Canonical's Launchpad platform. Any developer can publish a PPA. Used to get newer versions of software (e.g. the latest stable nginx, Python, or Git) than Ubuntu's default repositories offer.
# Add a PPA
sudo add-apt-repository ppa:ondrej/php
sudo apt update
sudo apt install php8.3
⚠ PPAs are community-maintained. Vet the maintainer before adding on production servers.
Maintained by the Fedora project. High-quality, well-tested packages not in RHEL's official channels — tools like htop, iftop, stress, jq, and many others. Widely trusted on enterprise systems.
# Enable EPEL
sudo dnf install epel-release -y
sudo dnf update -y
sudo dnf install htop jq -y
✓ EPEL is maintained by Red Hat engineers and is generally safe for production use.
# ── Ubuntu PPAs ───────────────────────────────────────────────────
# Add a PPA (automatically handles key import and sources.list.d entry)
sudo add-apt-repository ppa:ondrej/nginx
sudo apt update
# List all configured PPAs
grep -r "ppa:" /etc/apt/sources.list.d/
# Remove a PPA and downgrade packages to the distro version
sudo add-apt-repository --remove ppa:ondrej/nginx
sudo apt install ppa-purge
sudo ppa-purge ppa:ondrej/nginx
# ── RHEL / Rocky EPEL ─────────────────────────────────────────────
# Install EPEL release package (adds repo config and GPG key in one step)
sudo dnf install epel-release -y
# For RHEL (not Rocky) — enable the CodeReady Linux Builder repo first
sudo subscription-manager repos --enable codeready-builder-for-rhel-9-x86_64-rpms
# Verify EPEL packages available
dnf repolist epel
dnf search htop
# Install a package from EPEL explicitly
sudo dnf install --enablerepo=epel htop -y# sudo add-apt-repository ppa:ondrej/nginx Repository: 'ppa:ondrej/nginx' Description: Nginx Mainline + modules Press [ENTER] to continue or Ctrl-C to cancel Adding repository. Fetching key: https://keyserver.ubuntu.com/pks/lookup?... gpg: key 14AA40EC0831756756D7F66C4F4EA0AAE5267A6C OK Updating package lists... # dnf repolist epel repo id repo name status epel Extra Packages for Enterprise Linux 9 enabled: 7,512
What just happened? add-apt-repository handled the full workflow automatically — fetched the PPA's GPG key from Ubuntu's keyserver, stored it, and created a sources.list.d entry. EPEL once enabled provides 7,512 additional packages — a substantial extension of what RHEL ships by default, all maintained to a standard compatible with enterprise production environments.
Repository Troubleshooting and Auditing
Repository problems — failed updates, missing packages, GPG errors — are common and follow predictable patterns. Knowing how to diagnose each error type and audit your repository configuration keeps software supply chain hygiene tight.
The repository's signing key is missing from your keyring. Fix: import the key using the method in Section 4 above. Never fix this by setting trusted=yes — that disables signature verification entirely.
The repository does not serve packages for this distribution version. Check that the codename (noble, jammy) or version in the URL matches the current OS release.
When two repos offer the same package, the package manager picks by priority or version. Investigate with apt-cache policy pkg or dnf --showduplicates list pkg.
Caused by an unmaintained repository or a system with a wrong clock. Check timedatectl for clock issues; remove the stale repo if it is abandoned.
# Diagnose which repositories provide a specific package (apt)
apt-cache policy nginx
# Show all versions of a package available across all repos (apt)
apt-cache showpkg nginx
# Find which repo installed the currently-installed version (dnf)
dnf info nginx
# Check for duplicate packages across repos (dnf)
dnf --showduplicates list nginx
# Audit all repository source files for unexpected entries
grep -r "^deb" /etc/apt/sources.list /etc/apt/sources.list.d/ 2>/dev/null
# Full repo audit on RHEL — list all repos with their URLs
dnf repolist -v | grep -E "Repo-id|Repo-baseurl"
# Force refresh of all repository metadata caches
sudo apt update --allow-releaseinfo-change # apt
sudo dnf makecache --refresh # dnf# apt-cache policy nginx
nginx:
Installed: 1.24.0-2ubuntu7
Candidate: 1.26.1-1~noble
Version table:
1.26.1-1~noble 500
500 https://ppa.launchpadcontent.net/ondrej/nginx/ubuntu noble/main amd64
*** 1.24.0-2ubuntu7 100
100 /var/lib/dpkg/status
1.24.0-2ubuntu7 500
500 http://archive.ubuntu.com/ubuntu noble/main amd64
What just happened? apt-cache policy showed a classic multi-repo situation: nginx 1.24 is currently installed (from the Ubuntu archive), but the Ondřej PPA offers 1.26.1. The Candidate line shows which version apt upgrade would install next — 1.26.1 from the PPA. This is exactly how to check what will change before running an upgrade.
Never Use trusted=yes or gpgcheck=0 to Silence GPG Errors
When a GPG verification error appears during apt update or dnf install, the instinctive shortcut is to add trusted=yes (apt) or gpgcheck=0 (dnf) to silence it. This completely disables signature verification for that repository — any package it serves, including malicious ones, will be installed without question. The correct fix is always to import the missing key properly. Treat a GPG error as a signal to investigate, not an inconvenience to suppress.
Lesson Checklist
sources.list.d file with signed-by=, then apt update
.repo file for dnf and use dnf config-manager to enable, disable, and add repositories without editing files manually
trusted=yes or gpgcheck=0 to suppress signature errors
apt-cache policy and dnf --showduplicates list to inspect which repository will be used to install or upgrade a package
Teacher's Note
Run apt-cache policy package-name before every significant package upgrade on production. It takes two seconds and shows you exactly which repository version will be installed, whether a PPA would override the distro version, and what the currently installed version is. This single habit prevents the most common class of "why did the upgrade change something unexpected?" incidents.
Practice Questions
1. You need to install PostgreSQL 16 on Ubuntu 24.04 using the official PostgreSQL apt repository. Write every command required — from importing the GPG key through to confirming the installation — using the secure modern approach with a dedicated keyring file.
2. During apt update you see the error: The following signatures couldn't be verified because the public key is not available: NO_PUBKEY ABC12345DEF67890. What does this mean, how do you fix it, and what should you never do to silence this error?
3. On a Rocky Linux 9 server, a junior admin wants to install htop but dnf install htop returns "No match for argument: htop". Explain why this happens and what single command would resolve it.
Lesson Quiz
1. What is the purpose of the signed-by= field in a Debian apt repository source entry?
2. On RHEL/Rocky Linux, what does setting enabled=0 in a .repo file do?
3. Which command shows you which repository the currently-installed version of nginx came from, and which version would be installed on the next apt upgrade?
Up Next
Lesson 24 — The Linux Boot Process
From power-on to login prompt — BIOS/UEFI, GRUB, the kernel, and systemd's startup sequence explained