Install the rpm package — it contains rpmbuild — then build with dependency checking off and the package format pinned to v4.
sudo apt install rpm # universe; ships /usr/bin/rpmbuild
rpmbuild --nodeps --define '_rpmformat 4' -ba ~/rpmbuild/SPECS/helloworld.specThose two extra flags are the whole difference between a package that installs on the RHEL box you built it for and one that does not. The rest of this page explains why, and corrects three things the previous version of this article got wrong.
Table of Contents
Ubuntu 26.04 ships rpm 6, and that changes the output
Querying the Ubuntu archive via the Launchpad API shows the jump: Ubuntu 24.04 (noble) publishes rpm 4.18.2+dfsg-2.1build2, while Ubuntu 26.04 (resolute) publishes rpm 6.0.1-1build1, in universe. Unpacking that package’s macros file shows the consequence:
%_rpmformat 6
%_binary_payload %[ %_rpmformat >=6 ? "w19.zstdio" : "w9.gzdio" ]So on Ubuntu 26.04 an unmodified rpmbuild -ba emits the v6 format with a zstd payload. Building the spec below with rpm 6.0.2 twice — once at the default, once with --define '_rpmformat 4' — the difference shows up in the package’s own dependency list:
$ rpm -qp --requires helloworld-1.0-1.aarch64.rpm # default (format 6)
rpmlib(LargeFiles) <= 4.12.0-1
rpmlib(PayloadIsZstd) <= 5.4.18-1
$ rpm -qp --requires helloworld-1.0-1.aarch64.rpm # --define '_rpmformat 4'
rpmlib(CompressedFileNames) <= 3.0.4-1
rpmlib(FileDigests) <= 4.6.0-1
rpmlib(PayloadFilesHavePrefix) <= 4.0-1rpmlib() dependencies are satisfied by the installing rpm binary itself, not by anything in the target’s package database. The v4 build asks for nothing newer than rpm 4.6. The default build demands zstd payload support. If the target’s rpm does not advertise that feature, the install fails on a dependency the user cannot install their way out of. Pin the format unless you know the target.
Why BuildRequires can never be satisfied on a Debian host
This is the correction that matters most, and the old version of this page missed it entirely. Run the spec unmodified and you get:
error: Failed build dependencies:
gcc is needed by helloworld-1.0-1.aarch64gcc is installed. rpm cannot see it. BuildRequires is resolved against the rpm database, and on a Debian-family host that database is empty — rpm -qa returns zero packages, because everything on the system was installed by dpkg. No amount of apt install build-essential will ever satisfy an rpm BuildRequires line. You pass --nodeps and take responsibility for the build dependencies yourself.
The old page’s troubleshooting section blamed missing dependency errors on “libraries Ubuntu does not install by default” and told you to apt install them. That advice cannot work.
%_topdir is already correct — delete that step
The old version told you to write ~/.rpmmacros containing %_topdir %(echo $HOME)/rpmbuild. Reading the macros file shipped in the Ubuntu rpm-common package:
%_topdir %{getenv:HOME}/rpmbuildThat is already the default. rpm --eval '%_topdir' confirms it at runtime. The .rpmmacros step sets the value to what it already was. Skip it, and skip the useradd makerpm step too — rpmbuild refuses to run as root by default anyway, and your own account is fine.
Create the tree, minus the ceremony:
mkdir -p ~/rpmbuild/{BUILD,RPMS,SOURCES,SPECS,SRPMS}BUILDROOT is not in that list and does not need to be. rpm 4.20 and later create a per-package build directory; the build log shows it making BUILD/helloworld-1.0-build/BUILDROOT on its own.
%{?dist} expands to nothing here
The spec’s Release: 1%{?dist} is copied from Fedora convention, where redhat-rpm-config defines %dist as .el9, .fc42 and so on. Ubuntu’s rpm package does not define it — grepping the shipped macros file finds only commented-out #%distribution and #%disttag lines. The result, from rpmspec -q on the spec below:
helloworld-1.0-1.aarch64Not 1.el9. If your release naming depends on the dist tag, set it explicitly: rpmbuild --define 'dist .el9' ....
A spec that builds
Name: helloworld
Version: 1.0
Release: 1%{?dist}
Summary: A simple Hello World program
License: GPL-2.0-or-later
URL: https://example.com
Source0: helloworld-1.0.tar.gz
BuildRequires: gcc
%description
A basic Hello World program written in C.
%prep
%setup -q
%build
gcc -o helloworld helloworld.c
%install
install -d $RPM_BUILD_ROOT/usr/local/bin
install -m 755 helloworld $RPM_BUILD_ROOT/usr/local/bin
%files
/usr/local/bin/helloworld
%changelog
* Tue Feb 18 2025 MakerPM <maker@example.com> - 1.0-1
- Initial RPM releaseThree changes from the old version. %defattr(-,root,root,-) is gone — it has been the default since rpm 4.4 and adds nothing. rm -rf $RPM_BUILD_ROOT at the top of %install is gone, because rpm already cleans the buildroot and the line would delete the directory install -d then has to recreate. And the changelog date is fixed: the old entry read Mon Feb 18 2025, which rpm rejects with
warning: bogus date in %changelog: Mon Feb 18 2025 MakerPM - 1.0-1because 18 February 2025 was a Tuesday. rpm checks the weekday against the date.
Do not use alien to test your own package
The old page suggested converting the RPM to a .deb with alien and installing it. That tests alien, not your package. It cannot tell you whether your Requires resolve on the target distribution, whether your scriptlets run, or whether your file ownership survives — which are the things that actually break. Inspect it instead, then install it on the real target:
rpm -qpi helloworld-1.0-1.x86_64.rpm # metadata
rpm -qpl helloworld-1.0-1.x86_64.rpm # file list
rpm -qp --requires helloworld-1.0-1.x86_64.rpm
rpm -qp --scripts helloworld-1.0-1.x86_64.rpmAnd the caveat that belongs on every cross-distro build page: rpm’s automatic dependency generator records what it finds on the build host. A binary linked on Ubuntu against glibc 2.42 gets a Requires that a RHEL 9 machine cannot meet, no matter how clean the spec is. If the package contains compiled code, build it in a container of the target distribution. Ubuntu’s rpmbuild is for producing correct packaging — noarch content, config bundles, spec development — not for producing target-ready binaries.
What was executed here
The build, the format comparison, the BuildRequires failure, the empty rpm database, the bogus-date warning and the %{?dist} expansion were all run on macOS 26.6 (arm64) using upstream rpm 6.0.2 from Homebrew — hence aarch64 in the output above instead of x86_64. The Ubuntu-specific facts (rpm 6.0.1 in resolute, 4.18.2 in noble, %_topdir, %_rpmformat, the absence of %dist, and /usr/bin/rpmbuild living in the rpm package) come from the Launchpad API and from unpacking the real rpm_6.0.1-1build1_amd64.deb and rpm-common packages out of the Ubuntu archive. No Ubuntu machine was used.
Check this yourself. Every command and every block of output on this page is reproduced by /verify/rpmbuild-ubuntu-create-rpm.sh. Download it and run it: it creates its own scratch files, prints one line per claim, cleans up after itself, and exits non-zero if any claim here turns out to be wrong. If it disagrees with this page, the page is wrong.
