Self-hosted service
pkgr/pkgr avatar
pkgr/pkgr

pkgr: turn any Heroku buildpack app into a deb or rpm

Package any app into deb or rpm packages, using heroku buildpacks

586 stars65 forksRubyMIT

At a glance

What is it?
pkgr is a Ruby gem that packages an application built with a Heroku buildpack into a Debian or RPM package, including init scripts, crons and logrotate rules, so a command line tool can be distributed as a system package instead of a set of installation instructions. It supports a long list of 64-bit distributions and ships Docker images for the most recent ones.
Who is it for?
pkgr fits a team that already deploys with Heroku buildpacks and wants the same artefact to install cleanly as a deb or rpm on a fleet of servers, since it handles the system integration pieces rather than only the binary. It does not fit a project whose build is a plain Makefile or a static site, since it depends on the buildpack model, and it does not fit a 32-bit target, which the supported distribution list excludes.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 15 days ago.
What is it written in?
Mainly Ruby, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.

Editorial analysis

What pkgr is for

The stated goal of pkgr is to make Debian or RPM packages out of any app, including init script, crons, logrotate and the rest of the service plumbing. The documentation calls it an excellent way to distribute apps or command line tools without complicated installation instructions.

That framing tells you what problem it solves. The Heroku buildpack model is very good at producing a self contained application directory with its dependencies vendored in, and that directory is what a lot of modern deploy tooling expects. What it does not give you is a .deb or an .rpm, and those are what a Linux distribution's package manager, and an operations team, actually want. pkgr bridges that gap.

The output is a real system package, not a tarball with a Makefile. That means the init script, the cron entries and the logrotate configuration are all inside the package, and installing it with apt or yum does the right thing, including on upgrade and on removal.

There is also a hosted service, Packager.io, which the documentation says is free for open source apps. The examples given for apps packaged with pkgr are Gitlab, OpenProject and Discourse, which is a useful signal about the scale of thing it handles.

The users are therefore teams with a buildpack-based application who need to hand it to a package repository, and small tools that want to be installable with a single apt or yum command.

Install as a gem or run it from a Docker image

There are two install routes, and the choice depends on what you have and what you are targeting.

Docker images are available for the most recent distributions, and the documentation says that if you do not have Docker or are interested in older distributions, you can install pkgr as a Ruby gem:

bash
    gem install pkgr

The important constraint is on the gem route, and the documentation is explicit about it: if using the ruby gem, you need to run it under the distribution you want to package for. The gem runs the packaging steps natively, so the host you run it on determines the init system, the package format and the system dependencies that end up in the package.

The Docker route removes that constraint, because the container is the target distribution. The documented invocations are:

bash
    docker run --rm -it -v $(pwd):/app -v /tmp/cache:/cache pkgr/ubuntu:20.04
    docker run --rm -it -v $(pwd):/app -v /tmp/cache:/cache pkgr/el:8
    docker run --rm -it -v $(pwd):/app -v /tmp/cache:/cache pkgr/debian:10

The two volume mounts are doing the work. The first mounts your current directory at /app, which is where the app repository goes. The second mounts a cache directory at /cache, which is where the compile cache lives between packaging runs, and the --compile-cache-dir option is the related knob for that.

The image names map to the distribution and its major version, and you can run the same command from any host operating system because the container supplies the target.

The package command and the options that matter

Packaging is one command against your app's repository:

bash
    pkgr package path/to/app/repo

The resulting .deb or .rpm package lands in your current working directory.

The full option list is long, and the documentation prints it in the standard help format:

bash
    $ pkgr help package
    Usage:
      pkgr package TARBALL|DIRECTORY

The options fall into a few groups. The buildpack is selectable with --buildpack, and --buildpack-list takes a file containing a list of buildpacks, with --buildpack taking precedence if given. The package metadata is the usual set: --changelog, --maintainer, --vendor, --architecture which defaults to x86_64, --version, --iteration, --license, --homepage, --home, --description and --category.

The version option is worth noting, because its description says that if a git directory is given, it will use the latest git tag available. That means a tagged repository produces a package versioned from the tag without you passing --version by hand.

The system integration options are the ones that make this more than a repackaged tarball. --runner forces a specific runner, with upstart-1.5 and sysv-lsb-1.3 given as examples. --logrotate-frequency accepts daily, weekly, monthly or yearly, and --logrotate-backlog sets the retention with a default of 14. --dependencies and --build-dependencies take space separated lists of system packages to install alongside and to require at build time.

There are also four lifecycle hooks, --before-precompile, --after-precompile, --before-install and --after-install, plus --before-remove and --after-remove. The first pair run on the build machine around the buildpack compilation, and the install and remove hooks run on the target machine around the package being installed, updated or uninstalled. Those hooks are how you get a package that behaves correctly under a package manager rather than merely installing.

The supported languages and distributions

The officially supported languages are Ruby, NodeJS, Python and Go. PHP is in beta and points at a specific buildpack, the pkgr heroku-buildpack-php repository on a buildcurl branch. The documentation also says you can point at other buildpacks and that they may just work, with a link to the documentation page on customizing the build.

The distribution list is long and worth reading carefully, because it defines what you can actually ship. All of it is 64-bit only, which the section heading states explicitly.

The Ubuntu entries run from Ubuntu 12.04 precise through 14.04 trusty, 16.04 xenial, 18.04 bionic, 20.04 focal, 22.04 jammy, 24.04 noble up to Ubuntu 26.04. The Debian entries run from Debian 7 wheezy through 8 jessie, 9 stretch, 10 buster, 11 bullseye, 12 bookworm and 13 trixie.

The RPM side covers RHEL and CentOS 6, 7, 8, 9 and 10, Suse Linux Enterprise Server 11, 12, 15 and 16, and Amazon Linux AMI 2014 and 2015.

The range of targets is the point of the project, since one .deb does not install everywhere. The consequence is that your application has to build and run on the oldest distribution you still ship to, which for a buildpack based build means the runtime and the native extensions in your dependency tree have to be old enough to compile there. That is the practical constraint behind the long list, and it is worth checking before you package anything.

Where pkgr is the wrong tool

The first limit is the dependency on the buildpack model. pkgr takes an application that builds with a Heroku buildpack and turns that into a package. If your project builds with a plain Makefile, a Cargo build or a static site generator, there is nothing here for it to package, and you are better off writing the .deb or .rpm directly or using nfpm or fpm, which wrap a binary you already have. The difference in approach is that pkgr reproduces a buildpack produced directory tree, so it inherits that model rather than replacing it.

The second is 64-bit only. The distribution list says so in the heading, and there is no 32-bit path in the documentation. A 32-bit target is not a solved problem here.

The third is the coupling to the oldest target. Because one package has to serve every distribution in your list, the practical floor is the oldest one. A Ruby app that will not run on the oldest supported distribution cannot be packaged for it, and pkgr does not offer a way to have a different dependency set per distribution.

The fourth is the maintenance surface of the hooks. The before and after install and remove hooks run on the target machine with root privileges during a package operation, which is the correct place to do things like creating users and directories, and also a place where a mistake breaks package management on a fleet of machines. The scripts are yours, but the failure lands on the machine you were about to upgrade.

Compare that with the hosted service at Packager.io, which does the same conversion with the packaging and the distribution list maintained for you. The trade is between doing it on your own infrastructure with a Ruby gem and a container, and having someone else run the build pipeline, at the cost of the service being in the loop.

Licence, repository state and how a release is cut

pkgr is released under the MIT licence, with the text in the LICENSE file at the repository root, and the package is published as a Ruby gem named pkgr. The MIT terms are permissive, so packaging an application with pkgr does not put the packaged application under any additional licence obligation, which is the part worth checking if you are redistributing someone else's build.

The repository is not archived, and the last push was on 2026-10-08, so the tree is current relative to today. It publishes no GitHub releases, which is normal for a gem, since the gem version is the release unit and the CHANGELOG.md at the root records the history.

The tree shows a project with a real test suite and a bit of release infrastructure. There is a spec/ directory, a Rakefile, a Gemfile, a pkgr.gemspec, a bin/ directory for the executable, lib/ for the code, data/ for the buildpack definitions, boxes/ and images/ for the packaging artefacts, and a .pkgr.yml file at the top level, which is the per project configuration that the tool reads.

The Makefile is minimal, with a single bump-ruby target that rewrites the pinned Ruby buildpack revision in the data/buildpacks directory for a given TAG, so buildpacks are versioned alongside the gem. That is a small but important detail: the Ruby buildpack the packaging uses is pinned, and it is updated by a scripted edit rather than by hand.

For an adopter, the practical upgrade path is to bump the gem version, run it against a scratch repository on the distributions you ship, and diff the resulting package contents. The --iteration option, which the documentation says you should keep at its default, means each run produces a distinct package revision, so a package built twice for the same upstream release is still a distinct artefact.

Editorial conclusion

pkgr fits a team that already deploys with Heroku buildpacks and wants the same artefact to install cleanly as a deb or rpm on a fleet of servers, since it handles the system integration pieces rather than only the binary. It does not fit a project whose build is a plain Makefile or a static site, since it depends on the buildpack model, and it does not fit a 32-bit target, which the supported distribution list excludes. Before you adopt it, run pkgr package against a scratch repository on your own target distribution and check the generated init script and package dependencies, and read the .pkgr.yml file in the tree, since that is where the per project configuration lives.

Frequently asked questions

How do I install pkgr?

Docker images are available for the most recent distributions, and if you do not have Docker or want an older distribution, install the Ruby gem with gem install pkgr. Using the gem means running it under the distribution you want to package for.

How do I package an app with pkgr?

Run pkgr package against your app's repository, for example pkgr package path/to/app/repo, and the resulting .deb or .rpm package is written to your current working directory. With a Docker image, mount your repository at /app and a cache directory at /cache, then pass the image name for your target distribution.

Which languages and distributions does pkgr support?

Ruby, NodeJS, Python and Go are officially supported, with PHP in beta. The distribution list covers Ubuntu from 12.04 to 26.04, Debian from 7 to 13, RHEL and CentOS from 6 to 10, Suse Linux Enterprise Server 11 to 16, and Amazon Linux AMI 2014 and 2015, all 64-bit only.

How does pkgr handle init scripts, crons and logrotate?

They are part of the produced package. The --runner option forces a specific runner such as upstart-1.5 or sysv-lsb-1.3, and --logrotate-frequency and --logrotate-backlog set the logrotate configuration. You can also add scripts to run before and after install, update, and removal on the target machine.

What licence does pkgr use and is there a hosted option?

pkgr is released under the MIT licence, with the terms in the LICENSE file at the repository root. A hosted service is available at Packager.io, which the documentation says is free for open source apps.

Official sources

  1. Issues
  2. License: MIT
  3. pkgr/pkgr on GitHub
  4. Project website
  5. README
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/pkgr-pkgr.svg)](https://hysenlabs.com/projects/pkgr-pkgr)

Was this useful?