fpm (Effing Package Management) builds .deb, .rpm and .apk packages from the same files, without becoming an expert in each distribution's native format. This guide shows how to install fpm, package a binary or a script as a Debian, RHEL and Alpine package, and add metadata, dependencies and installation scripts to it. It is written for administrators and developers who have already installed packages with apt or dnf. All outputs come from a real lab on Debian 13 (fpm 1.18.0, measured on 30 September 2026). The guide ends with a comparison with nfpm to choose the right tool.
What you will learn
- Install fpm and the required native tools
- Build a
.deb, a.rpmand a.apkfrom the same sources - Add metadata, dependencies and installation scripts
- Choose between fpm and nfpm
Prerequisites
- A binary, a script or a directory to package
- Ruby (fpm is written in Ruby) and the native tools of the target (
dpkgfor the.deb,rpmfor the.rpm)
Why fpm
Each distribution has its own package format and tools: dpkg-deb and the debian/ files on the Debian side, rpmbuild and .spec files on the RHEL side. Learning each one takes time. fpm starts from a source (-s) and produces a target (-t), hiding that complexity:
- Sources (
-s): a directory (dir), atararchive, a Ruby gem, a Python module, an npm package, or even an existing.deb/.rpmto convert. - Targets (
-t):deb,rpm,apk(Alpine),pacman,tar, and others.
The same set of files therefore gives a package for all the targeted distributions, by changing a single argument.
Install fpm
fpm installs as a Ruby gem, after the system packages. Also install the native tool of each format you want to produce.
sudo apt updatesudo apt install -y ruby ruby-dev build-essential# native tools of the wanted targets:sudo apt install -y dpkg # to produce .debsudo apt install -y rpm # to produce .rpmsudo gem install fpmsudo dnf install -y ruby ruby-devel gcc make rpm-buildsudo gem install fpmCheck the installation:
fpm --version1.18.0Build a .deb from a directory
Prepare the tree to package, then run fpm. Here, a script installed in /usr/local/bin:
mkdir -p src/usr/local/binprintf '#!/bin/sh\necho hello\n' > src/usr/local/bin/mytoolchmod 0755 src/usr/local/bin/mytool
fpm -s dir -t deb -n mytool -v 1.0.0 \ --description "Demo tool" --maintainer "Stephane" \ -C src usr/local/bin/mytool{:timestamp=>"2026-09-30T15:45:54.446769+0000", :message=>"Created package", :path=>"mytool_1.0.0_amd64.deb"}The -C src option (chdir) enters src before reading the files, so that the package tree starts at usr/local/bin. Check the result with the Debian tools:
dpkg -I mytool_1.0.0_amd64.deb # metadata Package: mytool Version: 1.0.0 Architecture: amd64 Maintainer: Stephane Description: Demo tooldpkg -c mytool_1.0.0_amd64.deb # contents-rwxr-xr-x 0/0 23 2026-09-30 15:45 ./usr/local/bin/mytool-rw-r--r-- 0/0 125 2026-09-30 15:45 ./usr/share/doc/mytool/changelog.gzThe real output also lists the parent directories (./usr/, ./usr/local/...), left out here for readability. The package can be installed locally with sudo apt install ./mytool_1.0.0_amd64.deb.
The same package as .rpm and .apk
To produce the other formats, only the -t target changes. The same file mapping gives a .rpm:
fpm -s dir -t rpm -n mytool -v 1.2.0 --iteration 1 \ --description "Demo tool" --maintainer "Stephane" --license MIT \ --depends bash \ src/usr/local/bin/mytool=/usr/local/bin/mytoolHere we use the source=destination syntax: the local file is placed at /usr/local/bin/mytool in the package. Check with the RPM tools:
rpm -qip mytool-1.2.0-1.x86_64.rpmName : mytoolVersion : 1.2.0Release : 1License : MITSummary : Demo toolAnd the Alpine format is generated the same way with -t apk:
fpm -s dir -t apk -n mytool -v 1.0 \ src/usr/local/bin/mytool=/usr/local/bin/mytool{:timestamp=>"...", :message=>"apk output does not currently sign packages.", :level=>:warn}{:timestamp=>"...", :message=>"It's recommended that your package be installed with '--allow-untrusted'", :level=>:warn}{:timestamp=>"...", :message=>"Created package", :path=>"mytool_1.0_noarch.apk"}Metadata, dependencies and scripts
Metadata is passed as options, identical whatever the target:
| Option | Role |
|---|---|
-n, -v | package name and version |
--iteration | build number (the RPM release, the debian_revision) |
-a | architecture (amd64, noarch...) |
--depends | a dependency (repeatable) |
--description, -m, --url, --license | description, maintainer, URL, licence |
fpm can also attach lifecycle scripts triggered at installation or removal: --before-install, --after-install, --before-remove, --after-remove.
printf '#!/bin/sh\necho post-install ran\n' > after.shfpm -s dir -t rpm -n mytool -v 1.2.0 --after-install after.sh \ src/usr/local/bin/mytool=/usr/local/bin/mytoolThe script is indeed embedded, which you can check on the RPM side:
rpm -qp --scripts mytool-1.2.0-1.x86_64.rpmpostinstall scriptlet (using /bin/sh):#!/bin/shecho post-install ranfpm or nfpm: which one to choose
nfpm (from the GoReleaser team) is the modern alternative. The choice depends on your context.
| Criterion | fpm | nfpm |
|---|---|---|
| Language / dependency | Ruby (gem) | standalone Go binary, zero dependencies |
| Configuration | imperative (CLI) | declarative (versioned nfpm.yaml) |
| Input sources | dir, gem, python, npm, deb, rpm... | files only (contents) |
| Targets | deb, rpm, apk, pacman, tar... | deb, rpm, srpm, apk, ipk, archlinux, msix |
| Package signing | .rpm only, via --rpm-sign which delegates to rpmbuild | native (deb, rpm, apk) |
| CI/CD integration | to be scripted | built into GoReleaser |
Put simply: nfpm shines in a reproducible CI/CD pipeline (YAML config, single binary, native signing), especially to distribute a Go binary. fpm remains unbeatable when the input is not a simple directory: converting a gem, a Python module or repackaging a .rpm as a .deb, which nfpm does not do.
Pitfalls to know
fpm simplifies the build, but it does not decide for you what the package must contain or how it will be distributed. The four points below are the ones that make a package fail once installed, on the user's machine, even though the build went through without errors:
- Ruby dependency: fpm requires Ruby and
ruby-dev, where nfpm is a standalone binary. In CI, plan for installing the gem. - Partial signing: fpm signs neither
.debnor.apkfiles. For a.rpm,--rpm-signpasses--signtorpmbuild, or you sign afterwards withrpm --addsign(rpm-signpackage). On the Debian side,dpkg-signo longer exists in Debian 13:debsigssigns a.deb, but APT does not check that signature, because trust relies on the repository signature. - Files in
/etc: the Debian tools often mark everything under/etcas a configuration file, which changes the behaviour during updates. Check your paths. - No dependency resolution:
--dependssets the metadata, but it is up to you to declare the right graph; fpm does not compute it.
Key points
- fpm builds
.deb,.rpmand.apkfrom the same sources: only-tchanges. - It installs as a Ruby gem and requires the native tool of each target (
dpkg,rpm). -s dir+source=destinationmapping is the most common mode;-Cchanges the base directory.- Metadata (
-n,-v,--depends,--license) and scripts (--after-install...) are common to all targets. - fpm does not sign
.debor.apkpackages, and signs a.rpmonly throughrpmbuild: sign after creation for public distribution. - For a reproducible Go pipeline, look at nfpm; to convert gem/python/npm, stay with fpm.
Knowledge check
Check that the essentials of this guide are acquired. The questions only cover what has just been explained here.
Knowledge check
Check what you have learned with this interactive quiz
How it works
- The timer starts when you click Start
- Multiple choice, true/false and short answer questions
- You can move between questions
- Detailed results are shown at the end
Starts the quiz and the timer
Review
(0/0)Score by domain
A domain groups several guides and gets several questions, so its score is more reliable than a per-topic score, which often rests on a single question.
Summary
Strengths
No topic passed at 80% on at least two questions yet.
Weak areas
No topic below the pass mark.
Recommended next
Hints before you try again?
Continue the course: Fix broken dependencies on Linux (APT and DNF)
A new full quiz with random questions
Work again only on the questions you missed