How to Package Scripts into Cross-Platform deb/rpm Installers with FPM

12 views 0 likes 0 comments 17 minutesOriginalTutorial

Stop manually copying files and configuring environments. Learn how to use FPM to package your scripts or applications into standard deb/rpm installers with a single command, enabling seamless cross-platform distribution for Linux systems.

#DevOps # Package Management # FPM # deb # rpm # Linux # Automation # Ruby
How to Package Scripts into Cross-Platform deb/rpm Installers with FPM

Say Goodbye to Manual Copying: Package Your Scripts into deb/rpm with FPM

Have You Faced This Scenario?

Last week, I wrote a log cleanup script for the team. The functionality was fine, but distributing it was a nightmare:

  • For colleagues on Ubuntu servers, I had to teach them how to place it in /usr/local/bin, configure a systemd service, and set up environment variables.
  • For those on CentOS, I had to repeat the process, dealing with different paths and package manager commands.
  • Then the test environment needed it, followed by staging...

Every deployment turned into a "hand-holding session," which was highly inefficient and prone to missed steps.

If you're still distributing internal tools or scripts via "file copying + manual configuration," this tutorial is for you. By the end, you'll be able to package any script or application into a standard deb/rpm installer with just one command. Recipients can simply run apt install ./your-tool.deb and have everything in place automatically: files deployed to the correct locations, permissions set, and dependencies declared.

1. Prerequisites

FPM is written in Ruby, so you only need Ruby and basic build tools in your environment. I verified this on Ubuntu 22.04 and CentOS 8, but it applies to other mainstream Linux distributions as well.

Item Minimum Requirement
Ruby >= 2.6
OS Linux / macOS
Basic Tools tar, gzip

If your system doesn't have Ruby pre-installed (many servers don't), you can quickly install it via your package manager:

bash 复制代码
## Ubuntu / Debian
sudo apt update
sudo apt install -y ruby ruby-dev gcc make

## CentOS / RHEL / Rocky
sudo dnf install -y ruby ruby-devel gcc make

Installing gcc and make is necessary because some Ruby gem dependencies for FPM require compiling native extensions. Without them, you'll hit mkmf.rb can't find header files for ruby. Installing them upfront saves troubleshooting time.

2. Install FPM

FPM is distributed via RubyGems, making installation straightforward:

bash 复制代码
sudo gem install fpm

Verify the installation:

bash 复制代码
$ fpm --version
1.15.1  # Version may vary; as long as it prints, installation succeeded

Why install via gem instead of apt/dnf? FPM updates frequently, and system repositories often lag behind. Using gem ensures you get a recent stable release, and its gem dependencies are well-managed with a low risk of conflicts.

3. Core Usage: Package with One Command

FPM's core philosophy is "tell me the source and target format, and I'll handle the rest." The basic syntax looks like this:

bash 复制代码
fpm -s <source_type> -t <target_format> -n <package_name> -v <version> [other_options] <source_path>

Here are the most frequently used parameters (we'll apply them in the walkthrough):

Parameter Description
-s Source type. dir (from a directory) is the most common.
-t Target format. deb or rpm.
-n Package name.
-v Version number.
-d Dependency declaration, e.g., -d "python3 >= 3.8".
--prefix Installation path prefix. Defaults to / if omitted.
-f Overwrites existing output files.

4. Practical Walkthrough: Packaging a Python Script into deb and rpm

Scenario Setup

Assume you've written a Python script app-health-check.py to monitor service health. Requirements:

  1. Install to /usr/local/bin/
  2. Generate a config file at /etc/myapp/health.conf
  3. Declare python3 as a dependency

Step 1: Prepare the Directory Structure

In your working directory, simulate the application layout:

bash 复制代码
mkdir -p myapp/usr/local/bin
mkdir -p myapp/etc/myapp
echo '#!/usr/bin/env python3
print("Health check OK")' > myapp/usr/local/bin/health-check
chmod +x myapp/usr/local/bin/health-check
echo '{"endpoint": "localhost:8080", "interval": 60}' > myapp/etc/myapp/health.conf

The benefit of using a directory to mimic the filesystem layout is that the directory structure maps 1:1 to the final installed paths. Wherever you want a file to end up, place it in the corresponding directory. FPM will preserve this structure in the package.

Step 2: Build a deb Package (Debian/Ubuntu)

bash 复制代码
fpm -s dir -t deb \
  -n myapp-health \
  -v 1.0.0 \
  --description "Service health check tool" \
  -m "Your Name <you@example.com>" \
  -d "python3 >= 3.8" \
  --url "https://github.com/you/myapp" \
  --license "MIT" \
  -f \
  -C myapp \
  usr/local/bin/health-check etc/myapp/health.conf

Key parameters explained:

  • -C myapp changes the working directory to myapp before packaging. This ensures the output package contains /usr/local/bin/health-check instead of myapp/usr/local/bin/health-check.
  • -d "python3 >= 3.8" declares runtime dependencies. The installer will automatically check for them and block installation if they're missing.
  • --license and --url are embedded into the package metadata, aiding future audits and tracking.

After execution, you'll see myapp-health_1.0.0_amd64.deb in the current directory.

Step 3: Build an rpm Package (CentOS/RHEL/Rocky)

bash 复制代码
fpm -s dir -t rpm \
  -n myapp-health \
  -v 1.0.0 \
  --description "Service health check tool" \
  -m "Your Name <you@example.com>" \
  -d "python3 >= 3.8" \
  --license "MIT" \
  -f \
  -C myapp \
  usr/local/bin/health-check etc/myapp/health.conf

The parameters are nearly identical; we simply swapped -t deb for -t rpm. This is FPM's greatest value: the exact same source files output different platform packages by changing just one flag. You'll get myapp-health-1.0.0-1.x86_64.rpm.

Step 4: Installation & Verification

bash 复制代码
## Debian/Ubuntu
sudo dpkg -i myapp-health_1.0.0_amd64.deb

## CentOS/RHEL
sudo rpm -ivh myapp-health-1.0.0-1.x86_64.rpm
## Or use yum/dnf (recommended for automatic dependency resolution)
sudo dnf install -y ./myapp-health-1.0.0-1.x86_64.rpm

Test it out:

bash 复制代码
$ health-check
Health check OK
$ ls /etc/myapp/health.conf
/etc/myapp/health.conf

Files are in place, permissions are correct, and execution works out of the box. That's exactly what a standard package should do.

5. Common Pitfalls & Troubleshooting

1. rpmbuild: command not found when installing an RPM

This means your system lacks the rpm-build tool. FPM invokes rpmbuild internally to construct RPMs. Fix it with:

bash 复制代码
## CentOS/RHEL/Rocky
sudo dnf install -y rpm-build

## Ubuntu
sudo apt install -y rpm

2. Extra Directory Layer After Installation

If you forget the -C <source_dir> flag or write paths incorrectly, files might install to /myapp/usr/local/bin/ instead of /usr/local/bin/. Remember: -C changes the build context directory, not the output directory.

3. Missing Executable Permissions on Scripts

FPM preserves original file permissions. However, if you didn't mark files as +x in git, or created them on Windows before transferring to Linux, permissions might drop. Run chmod +x before packaging, or use --before-install to run a chmod script during installation.

4. Auto-Start Services on Installation?

Use the --after-install flag to specify a post-installation script:

bash 复制代码
fpm ... --after-install ./post-install.sh

Inside post-install.sh, you can add systemctl enable myapp && systemctl start myapp.

6. Conclusion

Let's recap today's workflow:

  1. Prepare the directory — Organize files according to their final installation paths.
  2. Run one fpm command — Specify source type, target format, package name, version, and dependencies.
  3. Swap the -t flag — Output deb or rpm from the same source files.
  4. Verify installation — Deploy with dpkg -i or yum install in one step.

For advanced usage (packaging from tarballs, modifying existing package metadata, stripping install scripts, etc.), check out the official docs at fpm.readthedocs.io.

Next Steps: Try packaging a small utility you've recently written. Even a few scripts plus a config file, once packaged as deb/rpm and shared with colleagues, will noticeably improve your distribution efficiency and deployment reliability. Once comfortable, dive into --before-install/--after-install hooks and --deb-systemd for automated systemd service generation.

Got packaging questions? Drop a comment below—I usually reply in the comments section.

Last Updated:

Comments (0)

Post Comment

Loading...
0/500

No comments yet, be the first to comment!