$linuxjunkies
>

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.

IntermediateUbuntuDebianFedoraArch9 min readUpdated June 7, 2026

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 .spec file(s)
  • BUILDworking 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. rpmbuild does not install them automatically in the host environment; mock does.
  • %autosetup — unpacks Source0 and 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}; the DESTDIR keeps 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.

tested on:Fedora 40Rocky 9RHEL 9

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