Criando pacotes .rpm para a família Red Hat

Criando pacotes .rpm

Na primeira parte desta dupla montamos pacotes .deb para Debian e Ubuntu. Agora é a vez da família Red Hat — RHEL, Fedora, Rocky, AlmaLinux, e por tabela openSUSE: vamos criar pacotes .rpm do zero, do spec file ao repositório dnf próprio, passando por rpmbuild, rpmlint, mock e assinatura GPG.

Como o RPM funciona

Diferente do .deb (um diretório que você monta na mão), o mundo RPM gira em torno de um único arquivo-receita: o spec file. Ele descreve metadados, dependências, como compilar, o que instalar e o changelog — e o rpmbuild faz o resto. Para inspecionar qualquer pacote:

rpm -qip htop-*.rpm      # metadados
rpm -qlp htop-*.rpm      # lista de arquivos
rpm -q --scripts htop    # scripts de instalação de um pacote instalado

Preparando a bancada

# Fedora / RHEL / Rocky / Alma
sudo dnf install rpm-build rpmdevtools rpmlint

# cria a árvore de trabalho em ~/rpmbuild
rpmdev-setuptree
tree ~/rpmbuild
# BUILD/ BUILDROOT/ RPMS/ SOURCES/ SPECS/ SRPMS/

Regra de ouro: nunca rode rpmbuild como root — um %install mal escrito pode escrever no sistema de verdade.

O spec file, seção por seção

Gere um esqueleto e vamos preencher (meuapp de novo como exemplo):

cd ~/rpmbuild/SPECS
rpmdev-newspec meuapp
Name:           meuapp
Version:        1.0.0
Release:        1%{?dist}
Summary:        Utilitário de exemplo do LinuxPro

License:        MIT
URL:            https://www.linuxpro.com.br
Source0:        %{name}-%{version}.tar.gz

BuildRequires:  gcc, make
Requires:       ca-certificates

%description
Descrição longa do pacote. Pode ter vários
parágrafos, sem a regra do espaço do Debian.

%prep
%setup -q                # desempacota o Source0 em BUILD/

%build
make %{?_smp_mflags}     # compila com paralelismo

%install
rm -rf %{buildroot}
install -D -m 0755 meuapp %{buildroot}%{_bindir}/meuapp
install -D -m 0644 meuapp.conf %{buildroot}%{_sysconfdir}/meuapp/meuapp.conf
install -D -m 0644 meuapp.1 %{buildroot}%{_mandir}/man1/meuapp.1

%files
%license LICENSE
%{_bindir}/meuapp
%config(noreplace) %{_sysconfdir}/meuapp/meuapp.conf
%{_mandir}/man1/meuapp.1*

%changelog
* Sat Sep 06 2026 Nilton Oliveira  - 1.0.0-1
- Primeira versao do pacote

Pontos que valem destacar:

  • %{?dist} no Release vira .el9, .fc42 etc. — o mesmo spec serve para várias distros
  • %config(noreplace) é o conffiles do mundo RPM: upgrade não sobrescreve a config editada (a nova chega como .rpmnew)
  • Use sempre as macros de caminho (%{_bindir}, %{_sysconfdir}, %{_unitdir}) em vez de caminhos fixos
  • Tudo que o %install criar precisa estar listado em %files — sobras causam erro de build (de propósito)

Construindo o pacote

# fonte na pasta certa
tar czf ~/rpmbuild/SOURCES/meuapp-1.0.0.tar.gz meuapp-1.0.0/

# build completo: binário + source RPM
rpmbuild -ba ~/rpmbuild/SPECS/meuapp.spec

ls ~/rpmbuild/RPMS/x86_64/   # meuapp-1.0.0-1.el9.x86_64.rpm
ls ~/rpmbuild/SRPMS/         # meuapp-1.0.0-1.el9.src.rpm

Instale e valide com o rpmlint (o “lintian” do RPM):

rpmlint ~/rpmbuild/RPMS/x86_64/meuapp-*.rpm
sudo dnf install ~/rpmbuild/RPMS/x86_64/meuapp-1.0.0-1.el9.x86_64.rpm
rpm -ql meuapp
sudo dnf remove meuapp

Scripts de instalação e usuário de sistema

Os equivalentes do postinst/prerm são %pre, %post, %preun e %postun. Um usuário de sistema, por exemplo:

%pre
getent passwd meuapp >/dev/null || 
  useradd --system --no-create-home --shell /sbin/nologin meuapp
exit 0

Serviço systemd tem macros prontas — instale a unit em %{_unitdir} e use:

BuildRequires: systemd-rpm-macros

%post
%systemd_post meuapp.service

%preun
%systemd_preun meuapp.service

%postun
%systemd_postun_with_restart meuapp.service

Dependências automáticas

O RPM escaneia os binários sozinho: bibliotecas linkadas viram Requires automaticamente (ex.: libssl.so.3()(64bit)). Você declara na mão só o que ele não enxerga — binários chamados via exec, arquivos de dados, etc. Binários Go/Rust estáticos passam quase sem dependências.

Build limpo com mock

Compilar na sua máquina “suja” esconde dependências de build faltantes. O mock constrói o pacote num chroot descartável da distro-alvo — é assim que o Fedora/EPEL constrói tudo:

sudo dnf install mock
sudo usermod -aG mock $USER && newgrp mock
mock -r rocky-9-x86_64 ~/rpmbuild/SRPMS/meuapp-1.0.0-1.el9.src.rpm
# resultado em /var/lib/mock/rocky-9-x86_64/result/

Com um SRPM e o mock, você gera o mesmo pacote para Rocky 9, Fedora 42 e openSUSE trocando só o -r.

Assinatura GPG

gpg --gen-key   # se ainda não tem chave
echo "%_gpg_name Nilton Oliveira " >> ~/.rpmmacros
rpm --addsign ~/rpmbuild/RPMS/x86_64/meuapp-*.rpm
rpm -K ~/rpmbuild/RPMS/x86_64/meuapp-*.rpm   # verifica

Repositório dnf próprio

sudo dnf install createrepo_c
mkdir -p /srv/repo/el9/x86_64
cp ~/rpmbuild/RPMS/x86_64/*.rpm /srv/repo/el9/x86_64/
createrepo_c /srv/repo/el9/x86_64/

Sirva com nginx e, no cliente, um .repo resolve:

cat | sudo tee /etc/yum.repos.d/meurepo.repo << 'EOF'
[meurepo]
name=Meu Repositorio
baseurl=https://repo.example.com/el9/$basearch/
enabled=1
gpgcheck=1
gpgkey=https://repo.example.com/chave.gpg
EOF
sudo dnf install meuapp

Publicou pacote novo? Rode createrepo_c --update e os clientes recebem via dnf upgrade.

Atalho: fpm

O mesmo fpm do post anterior também cospe RPM — para ferramenta interna, resolve em um comando:

fpm -s dir -t rpm -n meuapp -v 1.0.0 --description "Meu utilitário" 
  --depends ca-certificates meuapp=/usr/bin/meuapp

Resumo prático

  • Ferramenta interna? fpm ou um spec mínimo — rápido e suficiente
  • Publicando de verdade? spec completo + rpmlint limpo + build no mock + RPM assinado + repositório com createrepo_c
  • Config com %config(noreplace), systemd com as macros %systemd_*, caminhos sempre via macros

Com os dois posts da dupla — .deb e .rpm — você cobre praticamente todo servidor Linux em produção. Dúvidas ou um caso cabeludo de empacotamento? Me chame na página de contato.