Skip to content

FPM Packaging Guide

A comprehensive guide to packaging tools as Debian packages using FPM (Effing Package Management).


FPM (Effing Package Management) is a tool that makes it easy to create packages for multiple formats:

  • Debian (.deb) - Ubuntu, Debian, Mint
  • RPM (.rpm) - RedHat, CentOS, Fedora
  • Arch (.pkg.tar.zst) - Arch Linux
  • APK (.apk) - Alpine Linux
  • And many more…
  • ✅ Simple syntax - No need to learn complex packaging formats
  • ✅ Cross-platform - Generate packages for multiple distributions
  • ✅ Fast iteration - Quick package generation for testing
  • ✅ Scriptable - Easy to integrate into CI/CD pipelines

Terminal window
# Install FPM
gem install fpm
# Verify installation
fpm --version

If fpm command is not found after installation:

Terminal window
# Add gem binaries to PATH
export PATH="$(gem environment gemdir)/bin:$PATH"
# Add to your shell profile for persistence (~/.bashrc, ~/.zshrc)
echo 'export PATH="$(gem environment gemdir)/bin:$PATH"' >> ~/.bashrc

Depending on target package format, you may need:

Terminal window
# For Debian packages
sudo apt-get install build-essential
# For RPM packages
sudo apt-get install rpm
# For man pages (if packaging documentation)
sudo apt-get install pandoc

Terminal window
fpm [OPTIONS] [INPUT_FILES]
OptionDescriptionExample
-s TYPESource type (dir, gem, python, etc.)-s dir
-t TYPETarget package type (deb, rpm, etc.)-t deb
-n NAMEPackage name-n my-tool
-v VERSIONPackage version-v 1.0.0
--description TEXTPackage description--description "My awesome tool"
--depends PKGDependency package--depends curl
--maintainer NAMEMaintainer info--maintainer "Name <email@example.com>"
--license TYPESoftware license--license MIT
--architecture ARCHArchitecture (all, amd64, etc.)--architecture all
-p PATHOutput path-p packages/debian-ubuntu/
Terminal window
source_path=/destination_path

Examples:

Terminal window
# Single file
my-script=/usr/local/bin/my-script
# Directory (trailing slash copies contents)
my-docs/=/usr/share/doc/my-tool/
# Rename during packaging
README.md=/usr/share/doc/my-tool/documentation.md

Package a single shell script:

Terminal window
fpm -s dir -t deb \
-n hello-world \
-v 1.0.0 \
--description "Simple hello world script" \
--maintainer "Your Name <you@example.com>" \
--license MIT \
--architecture all \
-p packages/debian-ubuntu/ \
hello.sh=/usr/local/bin/hello

Result: Creates packages/debian-ubuntu/hello-world_1.0.0_all.deb

Package a tool that requires other packages:

Terminal window
fpm -s dir -t deb \
-n my-backup-tool \
-v 2.1.0 \
--description "Automated backup utility" \
--depends rsync \
--depends cron \
--maintainer "Admin <admin@example.com>" \
--license GPL-3.0 \
--architecture all \
-p packages/debian-ubuntu/ \
backup-tool=/usr/local/bin/backup-tool \
config/backup.conf=/etc/backup-tool/backup.conf

Example 3: Complete Application with Documentation

Section titled “Example 3: Complete Application with Documentation”

Package an application with docs and man pages (like netshare-mounter):

Terminal window
fpm -s dir -t deb \
-n netshare-mounter \
-v 1.0.0 \
--description "CLI utility to automatically configure SMB/CIFS share mounting on Linux systems using systemd mount units" \
--depends cifs-utils \
--maintainer "Mark Pamy <markkpamy@gmail.com>" \
--license "MIT" \
--architecture all \
-p packages/debian-ubuntu/ \
packages/debian-ubuntu/scripts/netshare-mounter/netshare-mounter=/usr/local/bin/netshare-mounter \
packages/debian-ubuntu/scripts/netshare-mounter/README.md=/usr/share/doc/netshare-mounter/README.md \
packages/debian-ubuntu/scripts/netshare-mounter/USAGE.md=/usr/share/doc/netshare-mounter/USAGE.md \
packages/debian-ubuntu/scripts/netshare-mounter/docs/=/usr/share/doc/netshare-mounter/reference/ \
packages/debian-ubuntu/scripts/netshare-mounter/man/netshare-mounter.1=/usr/share/man/man1/netshare-mounter.1 \
packages/debian-ubuntu/scripts/netshare-mounter/man/netshare-mounter-mount.1=/usr/share/man/man1/netshare-mounter-mount.1 \
packages/debian-ubuntu/scripts/netshare-mounter/man/netshare-mounter-unmount.1=/usr/share/man/man1/netshare-mounter-unmount.1 \
packages/debian-ubuntu/scripts/netshare-mounter/man/netshare-mounter-status.1=/usr/share/man/man1/netshare-mounter-status.1 \
packages/debian-ubuntu/scripts/netshare-mounter/man/netshare-mounter-validate.1=/usr/share/man/man1/netshare-mounter-validate.1

Package a Python application:

Terminal window
fpm -s dir -t deb \
-n my-python-app \
-v 1.5.0 \
--description "My Python application" \
--depends python3 \
--depends "python3-requests >= 2.0" \
--maintainer "Dev Team <dev@example.com>" \
--license Apache-2.0 \
--architecture all \
-p packages/debian-ubuntu/ \
app.py=/usr/local/bin/my-app \
lib/=/usr/local/lib/my-app/ \
config.yaml=/etc/my-app/config.yaml

Create both DEB and RPM packages:

Terminal window
# Create Debian package
fpm -s dir -t deb \
-n universal-tool \
-v 1.0.0 \
--description "Cross-platform tool" \
--maintainer "You <you@example.com>" \
--architecture all \
-p packages/debian-ubuntu/ \
tool=/usr/local/bin/tool
# Create RPM package
fpm -s dir -t rpm \
-n universal-tool \
-v 1.0.0 \
--description "Cross-platform tool" \
--maintainer "You <you@example.com>" \
--architecture all \
-p packages/rpm/ \
tool=/usr/local/bin/tool

Follow distribution conventions:

  • Name: Lowercase, hyphen-separated (e.g., my-tool, backup-utility)
  • Version: Semantic versioning (e.g., 1.0.0, 2.1.3)

Use standard Linux filesystem hierarchy:

TypeLocationExample
Executables/usr/local/bin/Main scripts/binaries
System binaries/usr/bin/System-wide tools
Configuration/etc/<package>/Config files
Documentation/usr/share/doc/<package>/README, guides
Man pages/usr/share/man/man1/Manual pages
Libraries/usr/local/lib/<package>/Supporting files
Data files/usr/share/<package>/Static resources

Be specific about dependencies:

Terminal window
# Good: Specify exact packages
--depends cifs-utils \
--depends systemd \
--depends "python3 >= 3.8"
# Bad: Vague or missing dependencies
--depends linux-utils # Too vague

Always include documentation:

Terminal window
README.md=/usr/share/doc/my-tool/README.md
LICENSE=/usr/share/doc/my-tool/LICENSE
CHANGELOG.md=/usr/share/doc/my-tool/CHANGELOG.md

Use proper email format:

Terminal window
--maintainer "Full Name <email@domain.com>"

Choose appropriate architecture:

  • all - Platform-independent (scripts, docs)
  • amd64 - 64-bit x86 binaries
  • arm64 - ARM 64-bit binaries
  • armhf - ARM hard-float

When updating packages:

Terminal window
# Patch release (bug fixes)
1.0.0 -> 1.0.1
# Minor release (new features, backwards compatible)
1.0.1 -> 1.1.0
# Major release (breaking changes)
1.1.0 -> 2.0.0

  1. Develop your tool:

    Terminal window
    packages/debian-ubuntu/scripts/my-tool/
    ├── my-tool # Main executable
    ├── README.md # Documentation
    └── src/ # Source files (if applicable)
  2. Generate package:

    Terminal window
    cd /mnt/e/GitRepos/ubuntu-server-config
    export PATH="$(gem environment gemdir)/bin:$PATH"
    fpm -s dir -t deb \
    -n my-tool \
    -v 1.0.0 \
    --description "Tool description" \
    --maintainer "Mark Pamy <markkpamy@gmail.com>" \
    --license MIT \
    --architecture all \
    -p packages/debian-ubuntu/ \
    packages/debian-ubuntu/scripts/my-tool/my-tool=/usr/local/bin/my-tool \
    packages/debian-ubuntu/scripts/my-tool/README.md=/usr/share/doc/my-tool/README.md
  3. Verify package:

    Terminal window
    # Show metadata
    dpkg-deb --info packages/debian-ubuntu/my-tool_1.0.0_all.deb
    # Show contents
    dpkg-deb --contents packages/debian-ubuntu/my-tool_1.0.0_all.deb
  4. Test installation:

    Terminal window
    # Install
    sudo dpkg -i packages/debian-ubuntu/my-tool_1.0.0_all.deb
    # Verify
    which my-tool
    my-tool --help
    # Uninstall
    sudo apt remove my-tool

Create a packaging script for consistent builds:

packages/debian-ubuntu/scripts/my-tool/package.sh:

#!/bin/bash
set -e
TOOL_NAME="my-tool"
VERSION="1.0.0"
REPO_ROOT="/mnt/e/GitRepos/ubuntu-server-config"
cd "$REPO_ROOT"
# Ensure FPM is in PATH
export PATH="$(gem environment gemdir)/bin:$PATH"
# Remove old package
rm -f "packages/debian-ubuntu/${TOOL_NAME}_${VERSION}_all.deb"
# Build package
fpm -s dir -t deb \
-n "$TOOL_NAME" \
-v "$VERSION" \
--description "My tool description" \
--maintainer "Mark Pamy <markkpamy@gmail.com>" \
--license MIT \
--architecture all \
-p packages/debian-ubuntu/ \
"packages/debian-ubuntu/scripts/${TOOL_NAME}/${TOOL_NAME}=/usr/local/bin/${TOOL_NAME}" \
"packages/debian-ubuntu/scripts/${TOOL_NAME}/README.md=/usr/share/doc/${TOOL_NAME}/README.md"
echo "Package created: packages/debian-ubuntu/${TOOL_NAME}_${VERSION}_all.deb"
# Show package info
dpkg-deb --info "packages/debian-ubuntu/${TOOL_NAME}_${VERSION}_all.deb"

Make it executable:

Terminal window
chmod +x packages/debian-ubuntu/scripts/my-tool/package.sh

Terminal window
# Debian packages
dpkg-deb --info package.deb
# RPM packages
rpm -qip package.rpm
Terminal window
# Debian packages
dpkg-deb --contents package.deb
# RPM packages
rpm -qlp package.rpm
Terminal window
# Debian packages
dpkg-deb --extract package.deb /tmp/extract/
# RPM packages
rpm2cpio package.rpm | cpio -idmv

Solution:

Terminal window
# Add gem binaries to PATH
export PATH="$(gem environment gemdir)/bin:$PATH"
# Verify
which fpm

Issue: “File already exists, refusing to continue”

Section titled “Issue: “File already exists, refusing to continue””

Solution:

Terminal window
# Remove existing package
rm packages/debian-ubuntu/my-tool_1.0.0_all.deb
# Or use --force flag (not recommended)
fpm --force ...

Issue: “Cannot chdir to ‘path’. Does it exist?”

Section titled “Issue: “Cannot chdir to ‘path’. Does it exist?””

Solution:

Terminal window
# Use absolute paths or run from repository root
cd /mnt/e/GitRepos/ubuntu-server-config
fpm -s dir -t deb ... [paths]

Issue: “No such file or directory” during packaging

Section titled “Issue: “No such file or directory” during packaging”

Solution:

Terminal window
# Verify source files exist
ls -la packages/debian-ubuntu/scripts/my-tool/my-tool
# Check for typos in paths

Solution:

Terminal window
# Install dependencies manually
sudo apt-get install -f
# Or reinstall package
sudo dpkg -i --force-depends package.deb
sudo apt-get install -f

Issue: Package installs but command not found

Section titled “Issue: Package installs but command not found”

Problem: Command not in PATH or missing execute permissions

Solution:

Terminal window
# Check installation location
dpkg -L package-name | grep bin
# Verify permissions in package
dpkg-deb --contents package.deb | grep bin

Raw fpm copies the source file’s mode verbatim, so a 0644 script installs unrunnable. Building through ./build.sh <package> avoids this — it stages the payload and applies modes from the manifest (0755 for bin/sbin destinations by default; see ../README.md). If you invoke fpm by hand, chmod +x the source first.


Run commands after package installation:

Terminal window
fpm -s dir -t deb \
... \
--after-install post-install.sh \
my-tool=/usr/local/bin/my-tool

post-install.sh:

#!/bin/bash
# Create necessary directories
mkdir -p /var/log/my-tool
chmod 755 /var/log/my-tool
# Enable systemd service
systemctl enable my-tool.service
systemctl start my-tool.service
Terminal window
fpm -s dir -t deb \
... \
--before-remove pre-remove.sh \
--after-remove post-remove.sh \
my-tool=/usr/local/bin/my-tool

Mark files as configuration (won’t be overwritten on upgrade):

Terminal window
fpm -s dir -t deb \
... \
--config-files /etc/my-tool/config.yaml \
my-tool=/usr/local/bin/my-tool \
config.yaml=/etc/my-tool/config.yaml
Terminal window
fpm -s dir -t deb \
-n my-tool \
-v 1.0.0 \
--url "https://github.com/user/my-tool" \
--category "utils" \
--vendor "My Company" \
--deb-priority "optional" \
...


Terminal window
# Basic Debian package
fpm -s dir -t deb -n name -v version file=/path
# With dependencies
fpm -s dir -t deb -n name -v version --depends pkg file=/path
# Multiple files
fpm -s dir -t deb -n name -v version file1=/path1 file2=/path2
# Directory contents
fpm -s dir -t deb -n name -v version dir/=/dest/
# Custom output location
fpm -s dir -t deb -n name -v version -p /output/dir/ file=/path
# Force overwrite
fpm --force -s dir -t deb -n name -v version file=/path
# List available source types
fpm -s help
# List available target types
fpm -t help
Terminal window
# Install package
sudo dpkg -i package.deb
# Remove package
sudo apt remove package-name
# List installed packages
dpkg -l | grep package-name
# Show package files
dpkg -L package-name
# Package info
dpkg -s package-name
# Fix broken dependencies
sudo apt-get install -f

  1. Read the examples - Start with simple packages and work up
  2. Package a test tool - Create a simple script and package it
  3. Test installation - Install, test, and uninstall your package
  4. Automate - Create packaging scripts for your tools
  5. Distribute - Share packages via repository or direct download

For more help, see the FPM documentation or ask in the repository issues.