Linux Administration Lesson 23 – Managing Software Repositories | Dataplexa
Section II — User, Process & Package Management

Managing Software Repositories

In this lesson

How repositories work apt sources on Debian/Ubuntu dnf repos on RHEL/Rocky GPG key management PPA and EPEL

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.

Remote Repository Server Packages (.deb / .rpm) Package Index / Metadata GPG Signature File Release / InRelease file HTTPS Package Manager apt / dnf 1. Fetch index 2. Verify GPG sig 3. Download package Local System Local package cache Installed package DB Trusted GPG keyring Repo source config files GPG signature verification ensures packages have not been tampered with between server and local install

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.

PPA — Ubuntu

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.

EPEL — RHEL/Rocky

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.

NO_PUBKEY
GPG key not found for repository

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.

404 Not Found
Repository URL is stale or wrong codename

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.

Conflicting packages
Multiple repos provide the same package name

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.

Release expired
Repository metadata has passed its validity date

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

I can add a third-party apt repository safely — GPG key import first, dedicated sources.list.d file with signed-by=, then apt update
I can write a .repo file for dnf and use dnf config-manager to enable, disable, and add repositories without editing files manually
I verify GPG key fingerprints against official vendor documentation before trusting them, and I never use trusted=yes or gpgcheck=0 to suppress signature errors
I know the difference between PPAs (Ubuntu community repos) and EPEL (RHEL enterprise extension), and I can enable EPEL on a Rocky Linux system with a single command
I use 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