Skip to content

Repository Change Control

In production environments, updating packages directly from public repositories introduces unpredictable risk. Repository change control uses frozen repo snapshots, local mirrors, and staged testing workflows to ensure update reliability.

List all enabled repositories
dnf repolist

View All Repositories (Including Disabled)

Section titled “View All Repositories (Including Disabled)”
List all repositories
dnf repolist all
View details for a specific repository
dnf repoinfo baseos
Disable a specific repository
dnf config-manager --disable epel
Enable a specific repository
dnf config-manager --enable epel
  • mirrorlist — Fetches a list of nearby mirrors from a mirror list service; may point to different servers and content each time
  • baseurl — Points to a fixed, specific repository URL, ensuring consistency

Switching from mirrorlist to a Fixed baseurl

Section titled “Switching from mirrorlist to a Fixed baseurl”
View the current repository configuration
cat /etc/yum.repos.d/almalinux-baseos.repo
Pin the BaseOS repository to a specific mirror
# Back up the original configuration
cp /etc/yum.repos.d/almalinux-baseos.repo /etc/yum.repos.d/almalinux-baseos.repo.bak
# Comment out mirrorlist and enable baseurl
sed -i 's/^mirrorlist=/#mirrorlist=/' /etc/yum.repos.d/almalinux-baseos.repo
sed -i 's/^# *baseurl=/baseurl=/' /etc/yum.repos.d/almalinux-baseos.repo

Pinning to a Specific Version (Version Snapshots)

Section titled “Pinning to a Specific Version (Version Snapshots)”

AlmaLinux and Rocky Linux vault repositories retain historical version snapshots.

Pin AlmaLinux BaseOS to version 9.3
cat > /etc/yum.repos.d/almalinux-baseos-frozen.repo << 'EOF'
[baseos-frozen]
name=AlmaLinux 9.3 - BaseOS (Frozen)
baseurl=https://repo.almalinux.org/vault/9.3/BaseOS/$basearch/os/
enabled=1
gpgcheck=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-AlmaLinux-9
EOF
Pin Rocky Linux BaseOS to version 9.3
cat > /etc/yum.repos.d/rocky-baseos-frozen.repo << 'EOF'
[baseos-frozen]
name=Rocky Linux 9.3 - BaseOS (Frozen)
baseurl=https://dl.rockylinux.org/vault/rocky/9.3/BaseOS/$basearch/os/
enabled=1
gpgcheck=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-Rocky-9
EOF

Disable the original repositories:

Disable default repos and use the frozen version
dnf config-manager --disable baseos
dnf config-manager --enable baseos-frozen

A local mirror is the best practice for production repository control.

Install repository sync and creation tools
dnf install -y createrepo_c dnf-plugins-core httpd
Create local mirror directories
mkdir -p /var/www/html/repos/{baseos,appstream,epel}
Sync the BaseOS repository
dnf reposync \
--repoid=baseos \
--download-metadata \
--destdir=/var/www/html/repos/baseos \
--newest-only
Sync the AppStream repository
dnf reposync \
--repoid=appstream \
--download-metadata \
--destdir=/var/www/html/repos/appstream \
--newest-only
Create repository metadata indexes
createrepo_c /var/www/html/repos/baseos/
createrepo_c /var/www/html/repos/appstream/
Configure Apache to serve the repository
cat > /etc/httpd/conf.d/repos.conf << 'EOF'
<Directory /var/www/html/repos>
Options Indexes FollowSymLinks
AllowOverride None
Require all granted
</Directory>
EOF
systemctl enable --now httpd
firewall-cmd --permanent --add-service=http
firewall-cmd --reload
Configure clients to use the local mirror
cat > /etc/yum.repos.d/local-mirror.repo << 'EOF'
[local-baseos]
name=Local Mirror - BaseOS
baseurl=http://repo-server.internal/repos/baseos/
enabled=1
gpgcheck=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-AlmaLinux-9
[local-appstream]
name=Local Mirror - AppStream
baseurl=http://repo-server.internal/repos/appstream/
enabled=1
gpgcheck=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-AlmaLinux-9
EOF
Disable public repos and use only the local mirror
dnf config-manager --disable baseos appstream
Create a repository sync script
cat > /usr/local/bin/sync-repos.sh << 'SCRIPT'
#!/bin/bash
set -e
LOG="/var/log/repo-sync.log"
REPO_BASE="/var/www/html/repos"
DATE=$(date '+%Y-%m-%d %H:%M:%S')
echo "[$DATE] Starting repository sync" >> "$LOG"
for repo in baseos appstream; do
echo "[$DATE] Syncing $repo ..." >> "$LOG"
dnf reposync \
--repoid="$repo" \
--download-metadata \
--destdir="$REPO_BASE/$repo" \
--newest-only \
--delete >> "$LOG" 2>&1
createrepo_c --update "$REPO_BASE/$repo/" >> "$LOG" 2>&1
done
echo "[$DATE] Repository sync completed" >> "$LOG"
SCRIPT
chmod +x /usr/local/bin/sync-repos.sh
Schedule weekly sync on Sundays at 3 AM
cat > /etc/cron.d/repo-sync << 'EOF'
0 3 * * 0 root /usr/local/bin/sync-repos.sh
EOF

Recommended update promotion path:

Development (Dev) -> Staging -> Pre-production -> Production

Each layer uses a repository snapshot from a different point in time.

Create a point-in-time repository snapshot
SNAPSHOT_DATE=$(date +%Y%m%d)
SNAPSHOT_DIR="/var/www/html/repos/snapshots/$SNAPSHOT_DATE"
mkdir -p "$SNAPSHOT_DIR"
# Use hard links to create the snapshot (saves disk space)
cp -al /var/www/html/repos/baseos/ "$SNAPSHOT_DIR/baseos"
cp -al /var/www/html/repos/appstream/ "$SNAPSHOT_DIR/appstream"
echo "Snapshot created: $SNAPSHOT_DIR"

Assigning Repository Snapshots to Environments

Section titled “Assigning Repository Snapshots to Environments”
Development environment uses the latest repo
# Dev: baseurl=http://repo-server.internal/repos/baseos/
Production environment uses a validated snapshot
cat > /etc/yum.repos.d/production.repo << 'EOF'
[prod-baseos]
name=Production BaseOS (Snapshot 20260315)
baseurl=http://repo-server.internal/repos/snapshots/20260315/baseos/
enabled=1
gpgcheck=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-AlmaLinux-9
EOF
  1. Discover — Monitor upstream security advisories and updates
  2. Assess — Analyze the impact scope and risk of each update
  3. Test — Validate in development/staging environments
  4. Approve — Go through the change approval process
  5. Execute — Push to production within a maintenance window
  6. Verify — Confirm services and functionality are intact
  7. Document — Record the change details and results
Generate a report of pending updates
#!/bin/bash
echo "========================================="
echo " System Update Assessment Report"
echo " Host: $(hostname)"
echo " Date: $(date)"
echo "========================================="
echo ""
echo "--- Current System ---"
cat /etc/redhat-release
echo "Kernel: $(uname -r)"
echo ""
echo "--- Available Updates ---"
dnf check-update 2>/dev/null || true
echo ""
echo "--- Security Update Summary ---"
dnf updateinfo summary 2>/dev/null
echo ""
echo "--- Security Advisory Details ---"
dnf updateinfo list security 2>/dev/null
Log update operations for audit trails
#!/bin/bash
CHANGE_LOG="/var/log/change-management.log"
TICKET_ID="${1:-UNTRACKED}"
echo "==============================" >> "$CHANGE_LOG"
echo "Change ticket: $TICKET_ID" >> "$CHANGE_LOG"
echo "Operator: $(whoami)" >> "$CHANGE_LOG"
echo "Timestamp: $(date)" >> "$CHANGE_LOG"
echo "Pre-update package state:" >> "$CHANGE_LOG"
rpm -qa --qf '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' | sort >> "$CHANGE_LOG"
echo "==============================" >> "$CHANGE_LOG"
# Perform the update
dnf update -y 2>&1 | tee -a "$CHANGE_LOG"
echo "Post-update package state:" >> "$CHANGE_LOG"
rpm -qa --qf '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' | sort >> "$CHANGE_LOG"
View recent change records
dnf history list
Export detailed information for a specific change
dnf history info <transaction-ID> > /var/log/change-$(date +%Y%m%d)-txn<transaction-ID>.log
List imported RPM GPG keys
rpm -qa gpg-pubkey* --qf '%{NAME}-%{VERSION}-%{RELEASE}\t%{SUMMARY}\n'
Import a third-party repository GPG key
rpm --import https://example.com/RPM-GPG-KEY-example
Verify an RPM package signature
rpm -K <package-file>.rpm
Check signatures of installed packages
rpm -qa --qf '%{NAME}-%{VERSION}-%{RELEASE}\t%{SIGPGP:pgpsig}\n' | head -20