The “multiple operating systems” part refers to the output package types. You’ll typically run FPM on a single build host (e.g., a Linux machine, or even a Docker container) to generate packages for Debian/Ubuntu, RedHat/CentOS, Arch Linux, etc.
Here’s a comprehensive guide on how to use FPM effectively.
—
What is FPM and Why Use It?
* Simplicity: Drastically simplifies the process of creating .deb or .rpm packages, which can otherwise be quite complex with dpkg-buildpackage or rpmbuild.
* Speed: Create a package in minutes, not hours.
* Flexibility: Package almost anything: a directory of files, a Python application, a Ruby gem, a pre-built binary, etc.
* Cross-Platform Output: Generate packages for Debian, Ubuntu, CentOS, RHEL, Fedora, Arch Linux, Solaris, macOS, and more, all from one command.
* Automation: Easily integrate into CI/CD pipelines.
Prerequisites
1. Ruby: FPM is a Ruby gem, so you need Ruby installed.
* On Debian/Ubuntu: sudo apt update && sudo apt install ruby ruby-dev build-essential
* On CentOS/RHEL: sudo yum install ruby ruby-devel @development (or dnf for newer versions)
* On macOS: Ruby is usually pre-installed, but it’s recommended to use a version manager like rbenv or rvm.
2. RubyGems: Ruby’s package manager, usually installed with Ruby.
Installation
Once Ruby is set up, install FPM:
sudo gem install fpm
(You might need sudo depending on your Ruby installation. If you’re using rbenv or rvm, you likely won’t need sudo.)
Core Concepts and Essential Flags
FPM’s command structure is generally:
fpm -s -t [options]
-s: What are you packaging from*?
* dir: (Most common) Package files from a directory.
* gem: Package a Ruby gem.
* python: Package a Python application (requires setuptools).
* rpm: Convert an RPM to another format.
* deb: Convert a DEB to another format.
* cpan: Package a Perl module.
* npm: Package a Node.js module.
* tar: Package a tarball.
* … and more.
* -t : What package format do you want to create?
* deb: Debian/Ubuntu package.
* rpm: RedHat/CentOS/Fedora package.
* tar: Generic tarball.
* zip: Generic zip archive.
* pkg: macOS package.
* pacman: Arch Linux package.
* msi: (Experimental) Windows Installer package.
* … and more.
* -n : The name of your package (e.g., my-custom-app).
* -v : The version of your package (e.g., 1.0.0).
* -p : (Optional) Where to put the generated package file. Defaults to current directory.
* -d ": Add a dependency (e.g., -d "nginx" or -d "python3 >= 3.6"). You can specify multiple -d flags.
* --description ": A long description for your package.
* --url ": The URL for your project.
* --license ": The license under which your software is distributed (e.g., MIT, GPLv3).
* --maintainer ": The person or team maintaining the package.
* --architecture : The target architecture (e.g., amd64, x86_64, all, noarch).
* --iteration : The package release number (e.g., 1, 2.el7). Useful for distinguishing package builds of the same software version.
* --prefix : Install files into this directory within the package (e.g., /opt/my-app). Defaults to / for dir source.
* -C or --chdir : Change to this directory before packaging. Very useful when packaging a directory.
* --input-type and --output-type: Aliases for -s and -t.
Step-by-Step Examples
Let’s say you have a simple application structure:
my-app-src/
├── bin/
│ └── myapp.sh
├── etc/
│ └── myapp.conf
└── lib/
└── myapp.py
And myapp.sh looks like this:
#!/bin/bash
echo "Hello from My App v1.0.0!"
cat /etc/myapp/myapp.conf
And myapp.conf:
APP_SETTING="default_value"
### 1. Preparing Your Files (Staging Directory)
It’s best practice to organize your files in a “staging” directory that mirrors the final installation path within the target system.
mkdir -p /tmp/my-app-staging/opt/myapp/bin
mkdir -p /tmp/my-app-staging/etc/myapp
cp my-app-src/bin/myapp.sh /tmp/my-app-staging/opt/myapp/bin/
cp my-app-src/etc/myapp.conf /tmp/my-app-staging/etc/myapp/
cp my-app-src/lib/myapp.py /tmp/my-app-staging/opt/myapp/lib/
Now, /tmp/my-app-staging looks like this:
/tmp/my-app-staging/
├── etc/
│ └── myapp/
│ └── myapp.conf
└── opt/
└── myapp/
├── bin/
│ └── myapp.sh
└── lib/
└── myapp.py
### 2. Creating a Debian (.deb) Package
fpm -s dir -t deb \
-n "my-custom-app" \
-v "1.0.0" \
--iteration "1" \
--architecture "amd64" \
--description "A simple custom application." \
--url "https://example.com/my-app" \
--license "MIT" \
--maintainer "Your Name " \
-d "bash" \
-d "python3" \
-C /tmp/my-app-staging/ \
.
-C /tmp/my-app-staging/: Tells FPM to change into this directory before* packaging.
* .: This tells FPM to package everything in the current directory (which is now /tmp/my-app-staging due to -C).
This will output a file like my-custom-app_1.0.0-1_amd64.deb.
To install and test:
sudo dpkg -i my-custom-app_1.0.0-1_amd64.deb
/opt/myapp/bin/myapp.sh
### 3. Creating a RedHat/CentOS (.rpm) Package
Using the exact same staging directory and almost the same command:
fpm -s dir -t rpm \
-n "my-custom-app" \
-v "1.0.0" \
--iteration "1.el7" \
--architecture "x86_64" \
--description "A simple custom application." \
--url "https://example.com/my-app" \
--license "MIT" \
--maintainer "Your Name " \
-d "bash" \
-d "python3" \
-C /tmp/my-app-staging/ \
.
* --iteration "1.el7": A common convention for RPMs indicating the release number and the target OS (Enterprise Linux 7).
* --architecture "x86_64": Standard for 64-bit RPMs.
This will output a file like my-custom-app-1.0.0-1.el7.x86_64.rpm.
To install and test:
sudo yum install my-custom-app-1.0.0-1.el7.x86_64.rpm
/opt/myapp/bin/myapp.sh
(Note: yum or dnf will handle dependencies automatically if they are in configured repositories.)
### 4. Adding Pre/Post-Install Scripts
You can execute scripts at various stages of the package lifecycle.
Create script files (e.g., preinstall.sh, postinstall.sh):
preinstall.sh:
#!/bin/bash
echo "Running pre-install script for my-custom-app..."
Example: Check for a condition before installation
if [ -f /opt/myapp/bin/myapp.sh ]; then
echo "My App already exists, upgrading."
else
echo "Installing My App for the first time."
fi
exit 0
postinstall.sh:
#!/bin/bash
echo "Running post-install script for my-custom-app..."
Example: Create a symlink or start a service
if [ ! -f /usr/local/bin/myapp ]; then
sudo ln -s /opt/myapp/bin/myapp.sh /usr/local/bin/myapp
echo "Created symlink /usr/local/bin/myapp"
fi
echo "Installation complete!"
exit 0
Now, add them to your FPM command:
fpm -s dir -t deb \
-n "my-custom-app" -v "1.0.0" --iteration "1" --architecture "amd64" \
--description "A simple custom application with scripts." \
--url "https://example.com/my-app" --license "MIT" \
--maintainer "Your Name " \
-d "bash" -d "python3" \
--before-install preinstall.sh \
--after-install postinstall.sh \
-C /tmp/my-app-staging/ \
.
--before-install : Runs before* files are installed.
--after-install : Runs after* files are installed.
* Other options: --before-remove, --after-remove, --before-upgrade, --after-upgrade.
### 5. Packaging a Python Application
If you have a Python project with a setup.py or pyproject.toml:
# In your python_app_root_dir/
fpm -s python -t deb \
-n "my-python-app" \
-v "0.5.0" \
--iteration "1" \
--architecture "all" \
--description "A Python application packaged by FPM." \
--python-bin /usr/bin/python3 \
--python-pip /usr/bin/pip3 \
. # Packages the current directory as a Python source
* --python-bin and --python-pip: Specify the Python executable and pip to use for building.
* --architecture "all": Often appropriate for pure Python apps.
Alternative (and often more robust) for Python apps:
1. Install your Python app into a staging directory:
mkdir -p /tmp/python-staging
pip install . --prefix=/usr/local --root=/tmp/python-staging
2. Then package the staging directory using fpm -s dir:
fpm -s dir -t deb \
-n "my-python-app" -v "0.5.0" --iteration "1" --architecture "all" \
--description "A Python application via dir source." \
-d "python3" \
-C /tmp/python-staging/ \
.
This approach gives you more control over the final file layout.
### 6. Marking Configuration Files
For files that users might modify (like myapp.conf), you want the package manager to handle upgrades gracefully (e.g., prompt to merge changes or preserve the user’s version).
fpm -s dir -t deb \
-n "my-custom-app" -v "1.0.0" --iteration "1" --architecture "amd64" \
--description "App with a config file." \
--url "https://example.com/my-app" --license "MIT" \
--maintainer "Your Name " \
-d "bash" -d "python3" \
--config-files /etc/myapp/myapp.conf \
-C /tmp/my-app-staging/ \
.
* --config-files : Marks the specified file as a configuration file.
Best Practices and Advanced Tips
* Use a Staging Directory (-C): Always build your package contents in a temporary directory (/tmp/my-app-staging in our example) that directly reflects the final installation path. This makes your FPM commands cleaner and less error-prone.
* Version Control: Keep your my-app-src and any build scripts under version control.
* Automation: Integrate FPM into your build scripts (e.g., Makefile, Rakefile, shell scripts) or CI/CD pipeline (Jenkins, GitLab CI, GitHub Actions) to automate package creation on every release or commit.
* Docker for Build Consistency: To ensure your packages are built consistently and don’t pick up host-specific dependencies, use Docker.
# Dockerfile for building packages
FROM ruby:3.1-slim-buster
RUN apt-get update && apt-get install -y build-essential
RUN gem install fpm
WORKDIR /build
COPY . /build
# Now run your fpm command inside the container
CMD ["fpm", "-s", "dir", "-t", "deb", "-n", "my-custom-app", ...]
* --prefix: If you want all your files to be installed under a specific directory (e.g., /opt/your-company/my-app), use --prefix /opt/your-company/my-app and then fpm -C /tmp/my-app-staging/opt/your-company/my-app ..
--exclude: For dir sources, you can exclude files or directories using --exclude ".git" --exclude ".log".
* Signing Packages: For production environments, you’ll want to sign your .deb and .rpm packages for security and integrity verification. FPM itself doesn’t directly sign, but you can integrate dpkg-sig or rpmsign into your post-FPM build steps.
* Complex Dependencies: For very complex dependency scenarios, you might still need to consult the native packaging tools’ documentation (dpkg or rpm). FPM handles most common cases well.
* Debugging: If your package isn’t working as expected, examine its contents:
* For .deb: dpkg -c package.deb (list contents), dpkg -I package.deb (show info).
* For .rpm: rpm -qpl package.rpm (list contents), rpm -qpi package.rpm (show info).
—
FPM is a powerful tool that significantly lowers the barrier to entry for creating system packages. By mastering the dir source type and understanding the common flags, you can efficiently package almost any application for multiple Linux distributions.