Build a .rpm Package
Learn to build .rpm packages step by step: set up the rpmbuild tree, write a spec file with BuildRequires, and produce clean builds with mock.
Before you start
- ▸A Fedora, Rocky Linux, or RHEL 9 system with sudo access
- ▸Basic familiarity with the shell and Makefiles
- ▸An upstream source tarball or your own application source code
Building an RPM package from source gives you control over installation paths, dependencies, compile-time flags, and distribution. Whether you are packaging an upstream tarball, patching existing software, or distributing an internal tool, the process follows the same skeleton: a source tarball, a spec file, and rpmbuild or mock to assemble the package. This guide walks you through the full workflow on Fedora and RHEL-family systems (Rocky Linux 9 / RHEL 9).
Install the Build Tools
You need rpm-build, rpmdevtools, and optionally mock for isolated builds. Never build as root.
Fedora
sudo dnf install -y rpm-build rpmdevtools rpmlint mock
sudo usermod -aG mock "$USER"
newgrp mock
Rocky Linux / RHEL 9
sudo dnf install -y rpm-build rpmdevtools rpmlint
sudo dnf install -y mock
sudo usermod -aG mock "$USER"
newgrp mock
On RHEL 9 you may need to enable the CRB (CodeReady Builder) repo first:
sudo subscription-manager repos --enable codeready-builder-for-rhel-9-x86_64-rpms
# or on Rocky Linux:
sudo dnf config-manager --set-enabled crb
Set Up the rpmbuild Tree
rpmdevtools provides rpmdev-setuptree, which creates the standard directory layout under your home directory. Run it once per user account.
rpmdev-setuptree
ls ~/rpmbuild/
Output (will vary slightly):
BUILD BUILDROOT RPMS SOURCES SPECS SRPMS
- SOURCES — original tarballs and patches
- SPECS — your
.specfile(s) - BUILD — working directory during compilation
- BUILDROOT — fake root where files are staged before packaging
- RPMS — finished binary RPMs
- SRPMS — source RPMs
Prepare a Source Tarball
Use a real upstream project to keep things concrete. This guide packages a tiny C program called hello-rpm to illustrate every field without distraction. Create the source structure and archive it:
mkdir -p hello-rpm-1.0
cat > hello-rpm-1.0/hello.c <<'EOF'
#include <stdio.h>
int main(void) { puts("Hello from RPM!"); return 0; }
EOF
cat > hello-rpm-1.0/Makefile <<'EOF'
PREFIX ?= /usr/local
all: hello
hello: hello.c
$(CC) $(CFLAGS) -o $@ $<
install: hello
install -Dm755 hello $(DESTDIR)$(PREFIX)/bin/hello-rpm
EOF
tar czf ~/rpmbuild/SOURCES/hello-rpm-1.0.tar.gz hello-rpm-1.0
rm -rf hello-rpm-1.0
Write the Spec File
The spec file is the heart of the process. Use rpmdev-newspec to generate a template, then edit it:
rpmdev-newspec ~/rpmbuild/SPECS/hello-rpm.spec
Replace the generated content with the following complete spec:
cat > ~/rpmbuild/SPECS/hello-rpm.spec <<'EOF'
Name: hello-rpm
Version: 1.0
Release: 1%{?dist}
Summary: A minimal RPM packaging example
License: MIT
URL: https://example.com/hello-rpm
Source0: %{name}-%{version}.tar.gz
BuildRequires: gcc
BuildRequires: make
%description
hello-rpm prints a greeting and demonstrates rpmbuild packaging.
%prep
%autosetup
%build
%make_build
%install
%make_install PREFIX=%{_prefix}
%files
%{_bindir}/hello-rpm
%changelog
* Mon Jan 01 2024 Your Name <[email protected]> - 1.0-1
- Initial package
EOF
Key Spec Fields Explained
- Release: 1%{?dist} — appends the distro tag (e.g.,
.fc40,.el9) automatically. - BuildRequires — packages that must be installed at build time.
rpmbuilddoes not install them automatically in the host environment;mockdoes. - %autosetup — unpacks
Source0and applies any numbered patches (Patch0,Patch1, …) in order. - %make_build — expands to
make %{?_smp_mflags}, enabling parallel compilation. - %make_install — calls
make install DESTDIR=%{buildroot}; theDESTDIRkeeps files out of your live system during staging. - %files — every file the package owns must be listed. Unlisted files cause a build error.
Build with rpmbuild (Host Build)
A host build uses the packages installed on your current system. It is fast but can produce packages that accidentally depend on libraries only present on your machine.
rpmbuild -ba ~/rpmbuild/SPECS/hello-rpm.spec
The -ba flag builds both the binary RPM and the source RPM. Use -bb for binary only, -bs for source only.
On success, find your packages here:
ls ~/rpmbuild/RPMS/x86_64/
ls ~/rpmbuild/SRPMS/
Build with mock (Clean, Isolated Build)
mock spins up a minimal chroot, installs only the declared BuildRequires, builds the package, and tears the chroot down. This is the right approach for packages you intend to distribute, because it proves the build is reproducible from a clean state.
Choose a Config
Mock ships with configs for current targets. List available configs:
ls /etc/mock/ | grep -E 'fedora|rocky|rhel'
Build the SRPM First, Then Pass It to mock
# Build the source RPM on the host
rpmbuild -bs ~/rpmbuild/SPECS/hello-rpm.spec
# Feed it to mock (adjust config name as needed)
mock -r fedora-40-x86_64 ~/rpmbuild/SRPMS/hello-rpm-1.0-1.fc40.src.rpm
Finished RPMs land in /var/lib/mock/fedora-40-x86_64/result/ by default. You can also cross-build for a different distro or architecture by swapping the -r argument.
Lint and Verify the Package
Run rpmlint against both the spec and the resulting RPM to catch common errors before distribution:
rpmlint ~/rpmbuild/SPECS/hello-rpm.spec
rpmlint ~/rpmbuild/RPMS/x86_64/hello-rpm-1.0-1.*.rpm
Install and exercise the package locally to confirm it works:
sudo rpm -ivh ~/rpmbuild/RPMS/x86_64/hello-rpm-1.0-1.*.rpm
hello-rpm
sudo rpm -e hello-rpm
Expected output from the binary:
Hello from RPM!
Troubleshooting
Unpackaged files error
If rpmbuild complains about files in BUILDROOT that are not listed in %files, either add them or use %exclude %{_datadir}/doc/... to discard them. You can also inspect what was installed:
find ~/rpmbuild/BUILDROOT/ -type f
Missing BuildRequires in host build
Install the missing package with dnf and re-run, or switch to mock, which resolves BuildRequires from the configured repo automatically.
mock permission denied
Confirm your user is in the mock group and that you started a new shell session after usermod:
groups | grep mock
Wrong %dist tag on SRPM
The %dist macro is set by the host system. When building for a different target with mock, the SRPM may carry your host's dist tag in its filename, but mock rebuilds it properly inside the chroot. This is cosmetic and does not affect the output RPM's dist tag.
Frequently asked questions
- What is the difference between rpmbuild -ba, -bb, and -bs?
- -ba builds both binary and source RPMs, -bb builds only the binary RPM, and -bs builds only the source RPM (SRPM). Use -bs when you want to hand the SRPM off to mock or a build system.
- Why should I use mock instead of building directly on the host?
- A host build can silently pick up libraries or tools that are not listed in BuildRequires, producing a package that fails on a clean system. Mock proves reproducibility by building inside a minimal chroot with only the declared dependencies installed.
- How do I add a patch to a spec file?
- Place the patch file in SOURCES, add a Patch0: mypatch.patch line in the header, and %autosetup will apply it automatically during %prep. For manual control use %patch -P 0 -p1 instead.
- How do I set compile flags like -O2 or enable a feature at build time?
- Export CFLAGS in the %build section or pass them directly to make: %make_build CFLAGS="%{optflags} -DFEATURE=1". The %{optflags} macro expands to the distro-recommended optimization flags.
- Can I build an RPM on a Debian or Ubuntu system?
- Yes. Install the rpm and rpm-build packages via apt, then follow the same spec and rpmbuild workflow. However, mock chroot configs are designed for RPM-based distros, so cross-distro mock builds are not straightforward; a Fedora or Rocky VM or container is the cleaner approach.
Related guides
AI and Artificial-Life Tools on Linux
Set up open-source AI/ML and artificial-life toolkits on Linux: PyTorch, JAX, DEAP, Avida, NetLogo, and RL environments with GPU driver guidance.
Assembly Language on Linux: A Starter Guide
Write x86-64 assembly on Linux from scratch: install NASM and GAS, learn syscalls, assemble and link a working program, then inspect and debug it.
How to Benchmark Disk Performance with fio
Learn to benchmark Linux disk performance with fio: writing job files, testing latency and throughput, and interpreting IOPS and percentile output correctly.
The Linux Boot Process Explained
Trace the full Linux boot sequence from UEFI firmware through GRUB2, the kernel, initramfs, and systemd to your login prompt — with diagnostics at each stage.