Introduction#
If you’ve worked with RHEL, Rocky, CentOS, or any of their downstreams, you’ve already seen RPMs — the standard way to distribute binaries in these ecosystems. They provide version tracking, dependency management, and consistent build and install rules across systems.
To build these packages cleanly, you usually want a reproducible environment that behaves exactly like your target system. That’s where mock (also known as mockbuild) comes in.
mock builds RPMs inside an isolated chroot (or container) environment. It installs all build dependencies automatically and ensures the resulting packages are built in a clean, minimal system. This eliminates the usual “it built fine on my machine” issue.
It’s also worth mentioning Koji, Fedora and RHEL’s official build system. Koji actually uses mock internally — the main difference is that Koji provides a distributed build farm with authentication, scheduling, and artifact management, while mock is the local standalone piece of that puzzle.
I’ve been using mock for tasks like rebuilding the RHEL kernel with local patches, building custom modules, and backporting packages from RHEL 9 to 8. It’s a solid tool once you get used to it, though not without its small annoyances.
Background#
Before diving deeper into mock, it’s worth understanding how an RPM build happens in the first place.
RPM builds are driven by spec files, which describe how to prepare, patch, build, install, and package the software.
They define everything from dependencies to %prep and %install stages. If you want to explore spec syntax, the RPM spec manual is a great reference.
When building software, you usually start with a source RPM (SRPM) — a package that contains the original source code, patches, and the spec file. Building an SRPM produces binary RPMs, following these general stages:
- Preparation: Extract source, apply patches.
- Build: Compile the source code.
- Install: Stage built files into a temporary directory tree.
- Package: Create the final
.rpmfiles with metadata and scripts.
This process is handled by rpmbuild in a local environment, or by mock in a clean, reproducible one.
My Verdict#
I used mock to build a patched RHEL kernel and some custom modules, and also to backport a few userspace packages. I’m still exploring some of its deeper internals, so I might miss certain nuances, but overall it’s been a useful and dependable tool.
Positives#
- Clean environment: every build happens in an isolated chroot or container.
- Reproducibility: ensures packages build correctly on a vanilla system.
- Container (podman/docker) and chroot modes: flexible for different setups.
- Automation: automatically installs build dependencies and sets up everything for you.
Negatives#
- Documentation: it has a very good man page, but the configuration explanations aren’t particularly enjoyable to read, and the lack of concrete examples makes things a bit painful.
- Customization: adding custom repositories, doing chain builds, or preparing staged builds for dependent packages can be slightly annoying. It’s possible, but not as straightforward as it should be.
Practice: Patching the RHEL Kernel#
Here’s a short walkthrough of using mock to patch and rebuild the RHEL kernel.
Download the kernel source RPM
dnf download --source kernel-coreYou’ll get something like following on RHEL9:
kernel-5.14.0-...el9_6.src.rpmExtract and prepare the source
rpmbuild -bp kernel-....src.rpmThis extracts and prepares the kernel source tree.
Create your patch Make the changes you want in the source tree, then create a patch using
diff.Update the spec file Add your patch to the list under
Patchentries and include it in the%prepsection. Also update the version/release and changelog.Build the new source RPM
rpmbuild -bs kernel.specBuild with mock
mock kernel-<new-version>.src.rpmThen wait for the build to complete — kernel builds take time. When it’s done, you’ll find the resulting binary RPMs under your result directory.
Useful Notes#
While debugging builds, I found a few options that help a lot:
Avoid cleanup: By default,
mockcleans the buildroot before and after each build. Use:mock -n # skip cleanup before mock -N # skip cleanup afterto save time during iterative testing.
Install packages inside buildroot:
mock -i <package>useful for installing debug tools without resetting the environment.
Run DNF inside buildroot:
mock --dnf-cmd "dnf makecache"Skip bootstrap stage:
mock --no-bootstrap-chrootThis reduces the build process to a single stage, making it faster for test runs.
Directory options:
--resultdir: output location for built RPMs--configdir: location of configuration files--rootdir: chroot/container root path
Man page: Honestly,
man mockis still the most complete and accurate documentation.
Closure#
Thanks for reading. I wrote this mostly to document my own experience using mock and the small details that helped me along the way.
If you’ve worked on similar setups or have better approaches, feel free to reach out — always happy to discuss.
