29.7 Creating a Local Repository with createrepo
Right, so you’ve decided to become a package hoarder. I get it. Maybe you’ve got a fleet of air-gapped servers that can’t phone home to the mothership (the internet), or perhaps you’ve meticulously curated a set of packages that you don’t want yum or dnf second-guessing from some remote repo. Whatever your reason, creating a local repository is the way to go. And the tool for the job is createrepo_c (the modern, C-based successor to the old Python createrepo). It’s the thing that takes a directory full of .rpm files and makes them intelligible to your package manager. It does this by generating the all-important repodata directory, which is basically a set of XML files (primary, filelists, others) that act as a database cataloging every package in your directory—their dependencies, their files, everything. Without this, yum and dnf will look at your pile of RPMs and shrug. They need that metadata to do their job.
Getting the Tools for the Job
First, you need the tool itself. On any modern Fedora, RHEL, or CentOS system, it’s a simple:
sudo dnf install createrepo_c
If you’re on an older system still using yum, well, first of all, my condolences. But also, you might find just createrepo in the repos. You want the _c version. It’s significantly faster, especially when you have thousands of packages. Trust me, you don’t want to wait for the Python version to chug through a large repository.
The Absolute Basics: Making a Repo
The simplest possible scenario. You have a directory, say /opt/my-repo, and you’ve lovingly placed some RPMs in it.
# Create the directory and copy some packages in
sudo mkdir -p /opt/my-repo
sudo cp /path/to/some/*.rpm /opt/my-repo/
# Now, become the wizard and generate the repodata
sudo createrepo_c /opt/my-repo/
That’s it. If you ls /opt/my-repo now, you’ll see a new repodata directory. Inside is the XML metadata and a bunch of .gz files for efficiency. Your package manager can now read this directory. But we’re just getting started. The real world is messier.
Dealing with Updates and Package Groups
Here’s the first “gotcha.” What happens when you add a new package to /opt/my-repo and run createrepo_c again? By default, it will blindly regenerate all the metadata from scratch. This is fine for a dozen packages, but if you have thousands, it’s a massive waste of time and CPU.
The --update flag is your friend here. It tells createrepo_c to check what’s already in the metadata and only add what’s new or changed. It’s dramatically faster.
# Add a new package and update the repo intelligently
sudo cp /path/to/new-package.rpm /opt/my-repo/
sudo createrepo_c --update /opt/my-repo/
Another killer feature is creating comps.xml, which defines package groups (like “GNOME Desktop” or “Development Tools”). This is what makes dnf groupinstall work. First, you need an XML file defining the groups. The format is a bit verbose, but you can often steal one from an existing repository (look in /repodata/*-comps.xml.gz on a mirror) and modify it.
# Assuming you've created a comps.xml file in your repo directory
sudo createrepo_c /opt/my-repo/ -g comps.xml
And, of course, use --update when modifying groups later.
The Critical Step: Configuring DNF to See Your Masterpiece
Creating the repo is only half the battle. Now you have to tell your system it exists. This means creating a .repo file in /etc/yum.repos.d/. This is where everyone, including the designers, agrees the syntax is a bit… odd.
Create a file like /etc/yum.repos.d/my-local.repo:
[my-local-repo]
name=My Awesome Local Repository
baseurl=file:///opt/my-repo
enabled=1
gpgcheck=0
Let’s talk about that gpgcheck=0 for a second. I’m telling you to disable GPG signature checking. This is, technically, a bad practice. It’s the equivalent of saying “I trust every package in this directory implicitly.” For a real production repo, you should sign your packages and set gpgcheck=1 along with a gpgkey= directive. But for a quick lab setup or internal testing, it’s the pragmatic choice. Just be aware you’re cutting a security corner.
Now, tell dnf to see your new repo:
sudo dnf makecache
You should see your my-local-repo in the list. You can now install packages from it directly: sudo dnf install my-cool-package.
Best Practices and Pitfalls
- Permissions: Your web server (if you serve this over HTTP) or the
rootuser needs read access to the directory and all the RPMs. This seems obvious, but I’ve seen people lock themselves out with overly restrictive permissions more times than I can count. - File Protocol vs. HTTP: I used
file://above for simplicity. For a single machine, it’s fine. For multiple machines, you need to serve this via a web server (nginx, Apache, httpd) and usebaseurl=http://your-server/path/. Thefile://protocol won’t work across the network. - Cleanliness: Your repository directory should only contain RPMs and the
repodatadirectory. If you throw a random text file or a tarball in there,createrepo_cwill ignore it, but it’s messy. Keep it clean. - The Nuclear Option: If something gets weird and your metadata seems corrupt (dnf is throwing strange errors about your repo), just blow away the
repodatadirectory and regenerate it from scratch without--update. It’s the equivalent of turning it off and on again, and it often works.