FPM Packaging Guide
A comprehensive guide to packaging tools as Debian packages using FPM (Effing Package Management).
Table of Contents
Section titled “Table of Contents”What is FPM?
Section titled “What is FPM?”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…
Why Use FPM?
Section titled “Why Use FPM?”- ✅ 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
Installation
Section titled “Installation”Using Ruby Gems (Recommended)
Section titled “Using Ruby Gems (Recommended)”# Install FPMgem install fpm
# Verify installationfpm --versionAdd to PATH (if needed)
Section titled “Add to PATH (if needed)”If fpm command is not found after installation:
# Add gem binaries to PATHexport PATH="$(gem environment gemdir)/bin:$PATH"
# Add to your shell profile for persistence (~/.bashrc, ~/.zshrc)echo 'export PATH="$(gem environment gemdir)/bin:$PATH"' >> ~/.bashrcPrerequisites
Section titled “Prerequisites”Depending on target package format, you may need:
# For Debian packagessudo apt-get install build-essential
# For RPM packagessudo apt-get install rpm
# For man pages (if packaging documentation)sudo apt-get install pandocBasic Usage
Section titled “Basic Usage”Command Structure
Section titled “Command Structure”fpm [OPTIONS] [INPUT_FILES]Essential Options
Section titled “Essential Options”| Option | Description | Example |
|---|---|---|
-s TYPE | Source type (dir, gem, python, etc.) | -s dir |
-t TYPE | Target package type (deb, rpm, etc.) | -t deb |
-n NAME | Package name | -n my-tool |
-v VERSION | Package version | -v 1.0.0 |
--description TEXT | Package description | --description "My awesome tool" |
--depends PKG | Dependency package | --depends curl |
--maintainer NAME | Maintainer info | --maintainer "Name <email@example.com>" |
--license TYPE | Software license | --license MIT |
--architecture ARCH | Architecture (all, amd64, etc.) | --architecture all |
-p PATH | Output path | -p packages/debian-ubuntu/ |
File Mapping Syntax
Section titled “File Mapping Syntax”source_path=/destination_pathExamples:
# Single filemy-script=/usr/local/bin/my-script
# Directory (trailing slash copies contents)my-docs/=/usr/share/doc/my-tool/
# Rename during packagingREADME.md=/usr/share/doc/my-tool/documentation.mdPackaging Examples
Section titled “Packaging Examples”Example 1: Simple Script
Section titled “Example 1: Simple Script”Package a single shell script:
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/helloResult: Creates packages/debian-ubuntu/hello-world_1.0.0_all.deb
Example 2: CLI Tool with Dependencies
Section titled “Example 2: CLI Tool with Dependencies”Package a tool that requires other packages:
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.confExample 3: Complete Application with Documentation
Section titled “Example 3: Complete Application with Documentation”Package an application with docs and man pages (like netshare-mounter):
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.1Example 4: Python Application
Section titled “Example 4: Python Application”Package a Python application:
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.yamlExample 5: Multi-Format Packaging
Section titled “Example 5: Multi-Format Packaging”Create both DEB and RPM packages:
# Create Debian packagefpm -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 packagefpm -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/toolBest Practices
Section titled “Best Practices”1. Package Naming
Section titled “1. Package Naming”Follow distribution conventions:
- Name: Lowercase, hyphen-separated (e.g.,
my-tool,backup-utility) - Version: Semantic versioning (e.g.,
1.0.0,2.1.3)
2. File Placement
Section titled “2. File Placement”Use standard Linux filesystem hierarchy:
| Type | Location | Example |
|---|---|---|
| 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 |
3. Dependencies
Section titled “3. Dependencies”Be specific about dependencies:
# Good: Specify exact packages--depends cifs-utils \--depends systemd \--depends "python3 >= 3.8"
# Bad: Vague or missing dependencies--depends linux-utils # Too vague4. Documentation
Section titled “4. Documentation”Always include documentation:
README.md=/usr/share/doc/my-tool/README.mdLICENSE=/usr/share/doc/my-tool/LICENSECHANGELOG.md=/usr/share/doc/my-tool/CHANGELOG.md5. Maintainer Information
Section titled “5. Maintainer Information”Use proper email format:
--maintainer "Full Name <email@domain.com>"6. Architecture
Section titled “6. Architecture”Choose appropriate architecture:
all- Platform-independent (scripts, docs)amd64- 64-bit x86 binariesarm64- ARM 64-bit binariesarmhf- ARM hard-float
7. Version Incrementing
Section titled “7. Version Incrementing”When updating packages:
# 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.0Working with This Repository
Section titled “Working with This Repository”Packaging Workflow for Scripts
Section titled “Packaging Workflow for Scripts”-
Develop your tool:
Terminal window packages/debian-ubuntu/scripts/my-tool/├── my-tool # Main executable├── README.md # Documentation└── src/ # Source files (if applicable) -
Generate package:
Terminal window cd /mnt/e/GitRepos/ubuntu-server-configexport 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 -
Verify package:
Terminal window # Show metadatadpkg-deb --info packages/debian-ubuntu/my-tool_1.0.0_all.deb# Show contentsdpkg-deb --contents packages/debian-ubuntu/my-tool_1.0.0_all.deb -
Test installation:
Terminal window # Installsudo dpkg -i packages/debian-ubuntu/my-tool_1.0.0_all.deb# Verifywhich my-toolmy-tool --help# Uninstallsudo apt remove my-tool
Automation Script
Section titled “Automation Script”Create a packaging script for consistent builds:
packages/debian-ubuntu/scripts/my-tool/package.sh:
#!/bin/bashset -e
TOOL_NAME="my-tool"VERSION="1.0.0"REPO_ROOT="/mnt/e/GitRepos/ubuntu-server-config"
cd "$REPO_ROOT"
# Ensure FPM is in PATHexport PATH="$(gem environment gemdir)/bin:$PATH"
# Remove old packagerm -f "packages/debian-ubuntu/${TOOL_NAME}_${VERSION}_all.deb"
# Build packagefpm -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 infodpkg-deb --info "packages/debian-ubuntu/${TOOL_NAME}_${VERSION}_all.deb"Make it executable:
chmod +x packages/debian-ubuntu/scripts/my-tool/package.shInspecting Packages
Section titled “Inspecting Packages”View Package Metadata
Section titled “View Package Metadata”# Debian packagesdpkg-deb --info package.deb
# RPM packagesrpm -qip package.rpmView Package Contents
Section titled “View Package Contents”# Debian packagesdpkg-deb --contents package.deb
# RPM packagesrpm -qlp package.rpmExtract Package
Section titled “Extract Package”# Debian packagesdpkg-deb --extract package.deb /tmp/extract/
# RPM packagesrpm2cpio package.rpm | cpio -idmvTroubleshooting
Section titled “Troubleshooting”Issue: “command not found: fpm”
Section titled “Issue: “command not found: fpm””Solution:
# Add gem binaries to PATHexport PATH="$(gem environment gemdir)/bin:$PATH"
# Verifywhich fpmIssue: “File already exists, refusing to continue”
Section titled “Issue: “File already exists, refusing to continue””Solution:
# Remove existing packagerm 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:
# Use absolute paths or run from repository rootcd /mnt/e/GitRepos/ubuntu-server-configfpm -s dir -t deb ... [paths]Issue: “No such file or directory” during packaging
Section titled “Issue: “No such file or directory” during packaging”Solution:
# Verify source files existls -la packages/debian-ubuntu/scripts/my-tool/my-tool
# Check for typos in pathsIssue: Dependencies not installing
Section titled “Issue: Dependencies not installing”Solution:
# Install dependencies manuallysudo apt-get install -f
# Or reinstall packagesudo dpkg -i --force-depends package.debsudo apt-get install -fIssue: 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:
# Check installation locationdpkg -L package-name | grep bin
# Verify permissions in packagedpkg-deb --contents package.deb | grep binRaw 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.
Advanced FPM Features
Section titled “Advanced FPM Features”Post-Install Scripts
Section titled “Post-Install Scripts”Run commands after package installation:
fpm -s dir -t deb \ ... \ --after-install post-install.sh \ my-tool=/usr/local/bin/my-toolpost-install.sh:
#!/bin/bash# Create necessary directoriesmkdir -p /var/log/my-toolchmod 755 /var/log/my-tool
# Enable systemd servicesystemctl enable my-tool.servicesystemctl start my-tool.servicePre/Post Removal Scripts
Section titled “Pre/Post Removal Scripts”fpm -s dir -t deb \ ... \ --before-remove pre-remove.sh \ --after-remove post-remove.sh \ my-tool=/usr/local/bin/my-toolConfiguration Files
Section titled “Configuration Files”Mark files as configuration (won’t be overwritten on upgrade):
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.yamlCustom Package Attributes
Section titled “Custom Package Attributes”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" \ ...References
Section titled “References”Quick Reference
Section titled “Quick Reference”Common FPM Commands
Section titled “Common FPM Commands”# Basic Debian packagefpm -s dir -t deb -n name -v version file=/path
# With dependenciesfpm -s dir -t deb -n name -v version --depends pkg file=/path
# Multiple filesfpm -s dir -t deb -n name -v version file1=/path1 file2=/path2
# Directory contentsfpm -s dir -t deb -n name -v version dir/=/dest/
# Custom output locationfpm -s dir -t deb -n name -v version -p /output/dir/ file=/path
# Force overwritefpm --force -s dir -t deb -n name -v version file=/path
# List available source typesfpm -s help
# List available target typesfpm -t helpUseful dpkg Commands
Section titled “Useful dpkg Commands”# Install packagesudo dpkg -i package.deb
# Remove packagesudo apt remove package-name
# List installed packagesdpkg -l | grep package-name
# Show package filesdpkg -L package-name
# Package infodpkg -s package-name
# Fix broken dependenciessudo apt-get install -fNext Steps
Section titled “Next Steps”- Read the examples - Start with simple packages and work up
- Package a test tool - Create a simple script and package it
- Test installation - Install, test, and uninstall your package
- Automate - Create packaging scripts for your tools
- Distribute - Share packages via repository or direct download
For more help, see the FPM documentation or ask in the repository issues.