简介RPM与DEB是Linux世界两大主流软件包管理体系分别广泛用于RHEL/Fedora/CentOS和Debian/Ubuntu等发行版。文档系统梳理了两类软件包的完整打包流程从目录结构搭建、control文件及spec文件编写到preinst/postinst/prerm/postrm等各阶段脚本的职责再到打包命令、安装升级与卸载操作适合Linux运维、系统管理员及需要定制内部软件的开发人员学习参考。内容以图文和示例配置相结合突出DEB打包时DEBIAN目录的组织方式以及RPM打包时BUILD/BUILDROOT/RPMS/SOURCES/SPECS/SRPMS标准目录的规划要点并给出dpkg -b、rpmbuild等常用打包命令与安装、升级、卸载的具体用法帮助读者快速上手产出可用的软件包。全部内容汇集在一份doc文档中大小267KB内容集中、层次清晰便于按需查阅目前已有417人学习浏览。掌握这两类打包机制学会编写规范的控制文件和spec脚本能有效提升软件分发、部署和版本维护效率是管理Linux服务器和构建自动化交付体系的重要基础技能。1. 为什么要学 RPM 和 DEB 打包两种软件包格式一套交付逻辑把源码丢给用户自己编译是分发软件最省事、也最伤用户体验的方式。真正到了交付环节你会发现 Linux 世界里基本只剩两种主流软件包格式rpm 系发行版认准.rpmdeb 系发行版认准.deb。RPM 和 DEB 软件包打包就是把可执行文件、动态库、配置、文档和安装卸载脚本组织成系统能识别的归档同时写清依赖、冲突、升级与卸载规则让用户一条命令装上、一条命令卸干净。这篇笔记把这条链路拆成几步走先讲清楚两种格式到底差在哪再分别用一个最小示例完成.rpm和.deb的构建接着把最容易翻车的坑逐个拆穿最后落到验证与版本同步。适合正在做内部工具分发、或准备把自己的软件正经交付给 Linux 用户的人新手能照步骤复现熟手也能在边界参数上少走弯路。2. RPM 和 DEB 的格式差异从用户命令到包内结构都要分开理解很多人以为 RPM 和 DEB 只是压缩格式不同换把工具就能产出来。实际接触之后你会发现这是两套独立设计的打包规范用户侧的安装命令、包内的元数据结构、依赖表达方式、配置文件的保护机制全都不一样。这一章先把这些差异摊开后面写 spec 和 debian 目录时你才知道每个字段在替谁说话。2.1 从用户视角看两种包安装、升级、卸载的动静差异用户装一个 rpm 包常见命令是rpm -ivh安装、rpm -Uvh升级、rpm -e卸载装 deb 则常见dpkg -i安装、dpkg -r卸载。直接用底层命令时两者都不会主动去解析依赖缺依赖就报错让你自己想办法。日常大家用dnf、yum或apt是因为包管理器在底层命令之上做了仓库和依赖解析。升级行为差异更大。rpm 的升级本质上是先装新版本再删旧版本的组合动作旧包里的%preun会执行新包的%post也会执行脚本顺序如果写错升级过程中可能把正在运行的服务停掉又拉不起来。deb 的升级则依赖dpkg的脚本时机preinst、postinst、prerm、postrm安装、升级、删除各自对应不同的参数脚本里必须按参数区分动作。卸载时用户观察到的差别rpm 卸载默认不会删配置文件除非你用rpm -e --purgedeb 的dpkg -P才会清配置文件。这类“包管理器默认行为”决定了你怎么组织安装脚本RPM 系更依赖%config标记保护配置deb 系则靠conffiles列出名单。所以做双格式打包不能只准备一套脚本要把每种格式的默认行为先摸清。2.2 包内部结构拆解rpm 的 headerpayload 与 deb 的 controldatarpm 包从结构上看由两大部分组成前面是 header保存了包名、版本、依赖关系、安装脚本、文件清单等元数据后面是 payload即用 cpio 归档后再压缩的实际文件。这也是为什么rpm -qip看信息很快因为只需要读 header不需要解压整个包。deb 包则是标准的 ar 归档里面固定有三个成员debian-binary记录格式版本号control.tar存放 control 文件、维护脚本和校验信息data.tar存放实际安装的文件。用户用dpkg-deb -I看元数据用dpkg-deb -c看文件清单本质上就是直接读归档里的不同成员。这里给一套常用的查看命令打包前和打包后都用得上# 查看 RPM 包的元数据与文件清单 rpm -qip demo-tool-0.1.0-1.x86_64.rpm rpm -qpl demo-tool-0.1.0-1.x86_64.rpm # 查看 DEB 包的元数据与文件清单 dpkg-deb -I demo-tool_0.1.0-1_amd64.deb dpkg-deb -c demo-tool_0.1.0-1_amd64.debrpm -qip的i表示 infop表示对包文件操作而不是查本地数据库l表示列出文件。dpkg-deb -I里的I是 info输出 control 和脚本内容-c是 list 的简写。多一步rpm -qp --scripts或dpkg-deb -e解出控制目录还能看到安装脚本是否和你写的一致。这些是上线前最直接的验证手段。两种格式的元数据字段习惯做成一张对照表来记忆用途RPM 字段DEB 字段包名NamePackage:二进制包名版本VersionReleaseVersion含修订号源码包名包名即源码名Source:编译依赖BuildRequiresBuild-Depends运行依赖RequiresDepends冲突声明ConflictsConflicts替换旧包ObsoletesReplaces配置文件标记%configconffiles文件rpm 的版本由Version和Release拼起来所以重打包只升Release很方便deb 的版本在Version一个字段里习惯写成0.1.0-1后面的修订号对应 rpm 的Release。架构名也不一样rpm 是x86_64deb 是amd64。写跨格式脚本时最容易漏的就是这些细节。2.3 环境准备安装工具链并初始化构建目录rpm 打包含rpm-build和rpmdevtools就够了。安装后执行rpmdev-setuptree会在家目录下建好标准目录BUILD、RPMS、SOURCES、SPECS、SRPMS。这个目录结构是 rpmbuild 默认的工作区约定源码压缩包放进SOURCESspec 文件放进SPECS构建产物输出到RPMS和SRPMS。deb 打包不依赖固定目录只要你把源码目录和debian/子目录放好即可构建工具用build-essential、debhelper和devscripts。下面是两边的初始化命令# rpm 系发行版 dnf install rpm-build rpmdevtools rpmdev-setuptree ls ~/rpmbuild/{BUILD,RPMS,SOURCES,SPECS,SRPMS} # deb 系发行版 apt install build-essential debhelper devscriptsrpmdev-setuptree仅仅是创建目录不会自动配置任何宏。如果你不想用家目录默认路径可以自己写~/.rpmmacros把%_topdir指到项目目录这样 CI 里便于整体清理。同一台机器上同时维护两套工具链没有冲突rpmbuild 和 dpkg-buildpackage 互不认识各管各的。配置自定义构建根的示例echo %_topdir /home/packager/rpmbuild ~/.rpmmacros%_topdir是 rpmbuild 最核心的宏之一所有中间目录都挂在它下面。改它之前先确认目录有写权限否则构建时会在mkdir阶段就失败。deb 侧没有对应的全局配置需求debian/目录里的文件就是全部配置这也是 deb 打包相对轻量的原因之一。3. 用 spec 文件打一个 RPM 包最小示例与三个必调参数RPM 打包的全部逻辑都写在 spec 文件里。它既描述包是什么也描述怎么编译、装哪些文件、装完执行什么脚本。第一次写 spec 的人容易被段落数量吓到其实核心执行段只有四段%prep、%build、%install、%files其余都是声明性内容。3.1 spec 文件骨架从 Name 到 %changelog 的分段职责spec 开头是包头字段Name、Version、Release三个字段组合出完整的包名。Release里常见的%{?dist}表示发行版后缀比如在 rpm 系发行版上构建会自动附加el9或fc40之类的标记方便同一版本在不同发行版上各自维护修订号。Source0指向源码压缩包文件名要能被rpmbuild在SOURCES目录里找到。BuildRequires声明编译期依赖Requires声明运行期依赖。很多人习惯把两者写成一样的实际编译依赖往往多于运行依赖比如构建时需要gcc但运行时只需要glibc。依赖写太多会拖累用户安装写太少会装完跑不起来后面第 5 章专门讲这个坑。执行段里%prep负责解压源码并应用补丁%setup -q是最常用的解压宏它会自动进入解压出的目录。%build通常执行make%install负责把文件安装到%{buildroot}目录下这里千万不能直接安装到系统的/usr/bin而是要先安到一个临时的构建根rpmbuild 打包时以这个构建根的内容为准。%files列出最终要装进包的文件可以是具体文件也可以是目录通配符。3.2 完整示例给一个 C 语言命令行工具打 RPM用一个最简单的 C 工具演示流程。假设demo-tool-0.1.0目录下有demo-tool.c和MakefileMakefile 的 install 目标支持DESTDIR变量PREFIX ? /usr all: cc -o demo-tool demo-tool.c install: install -d $(DESTDIR)$(PREFIX)/bin install -m 0755 demo-tool $(DESTDIR)$(PREFIX)/binDESTDIR是打包的标准接口没有它make install会把文件直接装到系统目录rpmbuild 无法收集文件列表。示例里PREFIX定义为/usr后续 deb 打包时同样走这个接口。对应的 spec 文件长这样Name: demo-tool Version: 0.1.0 Release: 1%{?dist} Summary: A command line tool for demonstration License: MIT URL: https://example.invalid/demo-tool Source0: %{name}-%{version}.tar.gz BuildRequires: gcc Requires: glibc %description A minimal demo package used to walk through RPM packaging. %prep %setup -q %build make %install make install DESTDIR%{buildroot} %files %{_bindir}/demo-tool %changelog * Fri Jan 01 2025 Packager packagerexample.invalid - 0.1.0-1 - Initial package把源码目录打成 tar.gz放进SOURCES再执行rpmbuild -batar czf ~/rpmbuild/SOURCES/demo-tool-0.1.0.tar.gz demo-tool-0.1.0/ rpmbuild -ba ~/rpmbuild/SPECS/demo-tool.spectar czf里的源码包顶层目录名必须与%{name}-%{version}完全一致%setup -q解压后要能直接cd进去。-ba表示同时构建二进制包和源码包src.rpm构建成功后二进制包在~/rpmbuild/RPMS/x86_64/源码包在~/rpmbuild/SRPMS/。如果只想快速确认文件列表对不对可以先rpmbuild -bp只跑%prep阶段避免每次全量编译。3.3 rpmbuild 构建参数-ba 与 -bb 的区别还有 --define 的用途rpmbuild的子命令对应不同阶段-bp只解压源码-bc执行到编译-bi安装到构建根-bb只产出二进制包-ba同时产出二进制包和 src.rpm。平时提交给 CI 用-ba最稳妥因为 src.rpm 是完整的可复现原料别人拿到它能重新构建。--define用于临时覆盖宏最常见的场景是指定额外变量。比如手动指定构建版本后缀rpmbuild -bb --define dist .el9 ~/rpmbuild/SPECS/demo-tool.spec--define的格式是键 值键值之间必须用空格不能用等号。它只对当前命令有效不会写进~/.rpmmacros。调试时可以多传几个--define覆盖 Release、覆盖%_topdir方便在非默认目录做试验。还有一个容易忽略的参数是--target。它能强制指定构建架构比如在 x86_64 机器上交叉构建 arm64 包时配合工具链使用。普通场景不用碰它但你要知道 rpmbuild 不是只能构建本机架构的包。验证产物时rpm -Uvh装上后立刻which demo-tool确认路径再跑一次程序确认动态库依赖没有缺失。4. 用 debian 目录打一个 DEB 包control、rules 与 dh 工具链deb 打包的思路和 RPM 完全不同rpm 把一切写进一个 spec 文件deb 则把信息拆进debian/目录的多个文件里。好处是职责清楚坏处是文件多而杂新手容易搞不清哪个文件在什么时机被谁读取。4.1 debian/ 目录里每个文件干什么control 与 rules 分权最关键的是control和rules。control声明包的元数据和依赖分两段Source段描述源码包Package段描述二进制包。Build-Depends对应 rpm 的BuildRequiresDepends对应 rpm 的Requires。rules是构建脚本本质上是一个可执行的 Makefile它定义build、install、binary等目标。changelog记录版本历史格式有严格规定dpkg-buildpackage会根据它生成debian/changelog的版本信息包的版本号由此而来。compat文件声明 debhelper 兼容级别也可以不写这个文件而在Build-Depends里写debhelper-compat ( 13)这是新版推荐做法少维护一个文件。其余文件按需出现install用来把构建产物复制进包目录不写则依赖dh_auto_install的默认行为conffiles列出需要保护的配置文件postinst、prerm等维护脚本负责安装、删除前后的动作。注意维护脚本必须带可执行权限否则 dpkg 会直接拒收。4.2 最小可复现示例同一份源码打成 .deb继续用上一章的demo-tool作为示例源码目录结构如下demo-tool-0.1.0/ ├── Makefile ├── demo-tool.c └── debian/ ├── changelog ├── compat ├── control └── rulesdebian/control内容Source: demo-tool Section: utils Priority: optional Maintainer: Packager packagerexample.invalid Build-Depends: debhelper ( 13), gcc Standards-Version: 4.6.2 Package: demo-tool Architecture: any Depends: ${shlibs:Depends}, ${misc:Depends} Description: A command line tool for demonstration A minimal demo package used to walk through Debian packaging.${shlibs:Depends}是输入变量构建时dh_shlibdeps会自动分析二进制文件的动态库依赖并填充成实际的Depends。${misc:Depends}由 debhelper 填充。手动写依赖容易漏这两个变量是 deb 系推荐的标准写法。debian/rules只写一行主规则%: dh $dh是 debhelper 的驱动脚本$是 make 自动变量代表当前目标的名称。这一行让dh自动执行一串预设命令包括dh_auto_configure、dh_auto_build、dh_auto_install、dh_install、dh_makeshlibs、dh_shlibdeps等。对这个普通 Makefile 项目dh_auto_install会执行make install DESTDIRdebian/demo-tool所以 Makefile 里支持DESTDIR这一点两种打包格式必须同时满足。debian/changelog的格式是硬性的demo-tool (0.1.0-1) unstable; urgencymedium * Initial package -- Packager packagerexample.invalid Fri, 01 Jan 2025 00:00:00 0000首行括号里的内容就是包版本号不能写错因为它是dpkg-gencontrol读版本的唯一来源。然后执行构建命令dpkg-buildpackage -us -uc-us跳过源码签名-uc跳过 changes 文件的签名。不加这两个参数本地没有 GPG 密钥时构建会在签名环节失败。产物在源码目录的上一级.deb文件命名格式是包名_版本号_架构.deb例如demo-tool_0.1.0-1_amd64.deb。4.3 dpkg-buildpackage 参数与 debhelper 的兼容级别dpkg-buildpackage的参数决定构建范围和行为-b只构建二进制包-S只构建源码包-us -uc控制签名。不加参数时默认同时做源码包和二进制包。第一次构建建议直接-b -us -uc产出一个.deb文件流程短也方便排查。debhelper 兼容级别写在compat文件里常见值为 10、11、12、13。级别影响dh_*命令的默认行为。比如 compat 9 及以下对.install文件的支持和文件名匹配规则略有差异compat 13 则默认启用更严格的路径检查。新项目直接用 13老项目升级前先看dh的变更说明盲目改 compat 会导致dh_auto_install的行为变化、文件装错位置。调试时给 rules 加一个环境变量能看得更清楚DH_VERBOSE1 dpkg-buildpackage -us -ucDH_VERBOSE1让dh把实际执行的每个命令都打印出来对排查dh_auto_install到底装了什么、装到哪是最直接的入口。构建完成后用dpkg -I和dpkg -c验证元数据与文件列表再dpkg -i安装测试一遍确认Depends被正确填充。拿到Depends实际值挺重要因为它能告诉你dh_shlibdeps有没有正常工作、动态库依赖有没有被正确抓取。5. 打包避坑与排查五个高频翻车现场的现象、原因与解法打包这事儿表面上命令跑通就有产物实际上细节全在“装上之后能不能跑、升级时会不会丢配置、卸载时干不干净”这些验证里。这一章记录我反复踩过的五个典型问题每条都按现象、原因、解决的顺序展开。5.1 现象一装上之后找不到动态库编译时明明好好的在打包机上程序跑得正常拷到另一台全新系统装完包一执行就报error while loading shared libraries: libfoo.so.1。原因是构建机环境太“脏”编译时某个依赖库系统里已经装了Requires没有声明真正干净的运行环境里库不存在。rpm 侧还有一个隐蔽原因%files忘了把软件自带的.so文件打进去或者只打了.so没打版本化文件名导致运行时按libfoo.so.1找不到。解决方法是编译结束后用readelf -d查看可执行文件的 NEEDED 段readelf -d demo-tool | grep NEEDED逐条对照Requires声明。rpm 侧用rpm -qpR看成品包的依赖必要时把软件自带的库文件作为%{_libdir}/libfoo.so.*写进%files。deb 侧尽量别手动写Depends让dh_shlibdeps帮你生成它会读取NEEDED并映射到系统内对应的包名。5.2 现象二升级时用户改过的配置被静默覆盖用户改了/etc/demo-tool.conf里的参数升级新版本后配置文件变成默认值用户改的配置全没了直接在线上翻车。原因是配置文件被打成了普通文件安装脚本在升级时会用新文件覆盖旧文件没有做配置保护。rpm 侧需要在%files里把该路径标记为%config(noreplace)deb 侧需要在debian/conffiles里列出该绝对路径一行一个。解决的关键是理解两种标记的差异rpm 的%config在旧配置被修改时会生成.rpmnew保留新内容而%config(noreplace)则直接保留旧文件、把新配置写成.rpmnewdeb 的conffiles机制类似dpkg 在升级时对比校验和发现用户改过就询问或保留。凡是用户可能手改的文件都用这套机制保护别怕麻烦。5.3 现象三DEB 包依赖写对却报“依赖关系无法满足”dpkg -i安装时报依赖关系无法满足: libfoo ( 2.0)但apt-cache policy libfoo查到的已装版本明明大于 2.0。常见原因有三个一是Depends里写了libfoo( 2.0)依赖包名和版本之间缺空格dpkg 解析时把整个字符串当成了包名二是把编译机才有、运行机没有的Build-Depends也抄进了Depends三是包名带了架构后缀比如写libfoo:amd64而实际包名不含:amd64。解决方法是先统一依赖写法包名 ( 版本)括号前后按规范留空格多个依赖用逗号分隔。然后逐条确认运行机真的需要这个库能用dpkg -s验证版本。临时用--force-depends装上是自救手段不是长期方案依赖关系还是要改对。5.4 现象四同一份代码打出两种包文件清单对不上rpm 包和 deb 包各装一台机器demo-tool能跑但一边有 man 手册另一边没有或者一边多了个空目录两边文件列表总有微妙的差异。原因在于%files和 deb 的文件收集机制是两套独立逻辑人肉维护必然漏。rpm 的%files要你一条条写deb 的dh_auto_install则把make install DESTDIRdebian/demo-tool的内容全部收进包。两边的“事实来源”看似相同实际维护时各写各的就分叉了。解决方法是只维护一份文件清单。我一般把Makefile的 install 目标作为唯一事实来源rpm 侧%files直接引用%{buildroot}下装出的文件deb 侧直接信任dh_auto_install的结果。如果有额外文件要收录用 deb 的.install文件或 rpm 的%files全局通配符尽量少在两边同时写具体文件路径。每次构建后各跑一次rpm -qpl和dpkg-deb -c对比输出不一致的立刻修。5.5 现象五打包脚本里写死了本机路径换机器就翻车构建设备上有/home/user01/thirdparty/libxxxspec 里%build或%install直接引用这个绝对路径构建时没问题拿到别的机器上运行就找不到库、找不到配置。原因是%install里直接对系统路径操作或者编译期把本地绝对路径编进了二进制没做 staging 隔离。rpm 的%{buildroot}和 deb 的debian/包名目录就是用来隔离的所有文件必须先落到这个隔离目录再被工具收集。解决方法是统一约束任何make install必须支持DESTDIR任何手工install -d必须拼上构建根变量绝不允许直接写/usr/bin、/etc这类裸路径。rpm 侧写成install -d %{buildroot}%{_bindir}deb 侧交给dh_auto_install并确认 Makefile 用的是$(DESTDIR)。编译参数里如果必须引用第三方库路径用--define或环境变量传入不要硬编码。6. 让打包可复现容器构建、静态检查与版本同步三个技巧打包脚本写好后最大的敌人是环境漂移。今天构建能过三个月后同一份 spec 因为系统升级了一个编译依赖就崩了本机能过同事机器上却缺库。我的习惯是一旦项目进入正式交付就把打包丢进容器环境所有依赖通过构建脚本显式安装禁止在容器里手敲dnf install或者apt install凑依赖。这样每次构建从空白环境开始BuildRequires少了哪个包构建立即报错而不是侥幸通过。# 进入容器后按构建文档逐条安装依赖然后执行 rpmbuild -ba ~/rpmbuild/SPECS/demo-tool.spec dpkg-buildpackage -us -uc容器跑完构建后顺手跑一次静态检查。rpm 侧用rpmlintdeb 侧用lintian这两个工具能查出元数据缺失、权限错误、重复文件、脚本语法不合法等问题rpmlint ~/rpmbuild/RPMS/x86_64/demo-tool-0.1.0-1.x86_64.rpm lintian demo-tool_0.1.0-1_amd64.deb输出里的E:是错误必须改W:是警告建议改I:是信息类提示。第一次跑会看到不少噪音比如缺少文档、changelog 缺失这类提醒不用全部消除重点盯E:和与文件路径、依赖相关的W:。版本同步也值得写进流程。rpm 用Version Releasedeb 用Version里的0.1.0-1两边经常出现 rpm 升到 0.2.0、deb 还停在 0.1.0 的情况。我的做法是在项目根目录放一个VERSION文件Makefile 里读它再通过 sed 生成或更新 spec 和debian/changelogVERSION : $(shell cat VERSION)发布时只改VERSION然后重新生成两份打包元数据从源头杜绝版本不一致。打包这件事最不值钱的是让rpmbuild -ba跑通最值钱的是让「换一台机器、隔半年、换个人」都能稳定产出一样的结果。我现在接到任何要交付的 Linux 工具第一件事是先把Makefile install目标和DESTDIR支持补上再在这套地基上套 spec 和 debian 目录省得每次打包都在文件清单上找补。希望帮到你。本文还有配套的精品资源点击获取