A simple guide for creating Debian Packages

By Martin Rusev read

According to Wikipedia Debian has 45000+ available packages, Ubuntu has over 50 000 - this is obviously something people have been doing for over 20 years and yet when you have to create a package for your own apps - it is extremely difficult and cumbersome process.

There is no doubt that the documentation is well written and detailed and you can find tutorials here and there, but as of this moment - you need a lot of domain specific knowledge to make something useful out of that information.

This guide is written from a Devops/developer perspective - I have been packaging my apps for Debian/Ubuntu/CentOS for 2 years and these are the tools I've been using to do it quickly and efficiently.

What is inside a Debian package?

At its core a Debian package has two major components:


# Application source
- yourapp.deb
|-/etc/init.d/app-daemon # daemon
|-/usr/bin/app # executable
|-/opt/local/yourapp # source

For example if your application has a daemon, that resides in /etc/init.d/app-daemon, before creating the debian package you put the file in a folderyourapp/etc/init.d/app-daemon and later when installing from the compiled deb file it will be automatically copied to /etc/init.d/app-daemon. The same goes for your application source, if you want to put it in /opt/local/yourapp, you create a folder yourapp/opt/local/yourapp and again - on install it is going to be automatically copied to /opt/local/yourapp


# Scripts, automatically executed on pre and post install and pre,post remove
preinst # Create users, set permissions, etc.
postinst

prerm
postrm

These scripts are written in bash and used to create users, install dependencies, clean up directories, set permissions, create log directories, etc.

FPM (Effing Package Management)

Once you have put your future package in the corresponding folders and have created the pre/post install/remove scripts, it is time to actually compile your app to a deb file. In my personal experience - even thought I spent a tremendous amount of time reading docs about how to do it, I've never created a debian package by hand. Instead - I've been using a great tool, called FPM. FPM is a command line tool and takes care of all the platform specific bits. Below you can see what an FPM command looks like.


fpm --epoch 1 -s dir -e -C /yourapp/debian \
-a all -m "Your Package " \
--description "My Package compiled to deb"\
-v 1.0 \
-t deb \
-n yourapp \
--post-install debian/postinst \
--post-uninstall debian/postrm \
--pre-uninstall  debian/prerm \

This is the most simple example for actually compiling a debian package. FPM can do a lot more than that, you can take a look at the comprehensive wiki for more details.

Automating with Makefiles

Once you compile your Debian package with FPM it is time for the next step - automating the process. You can do this with a simple bash script, but Makefiles are a better alternative if you have to this more often. Makefiles have autocomplete, so you can do: make tab and it will show you all the commands available in the file. Makefiles have some specifics, but you can manage them just fine with basic Bash skills.

This is what a basic Makefile looks like:


# Constants, just like bash
BUILD=build/package

# Commands - execute in the terminal with `make clean`
clean:
    rm -rf build
    rm -f *.deb

# Execute with `make build`
build: clean
    fpm -- ...

Testing with Docker

Finally you have your own deb package, but here comes the interesting part - does it work as expected on all the targeted distros? To know for sure we have to deploy test servers with each distro and test one by one. For a couple of years - I've used VirtualBox and Vagrant and they can definitely get the job done. The only problem I had with this setup was that it was a little bit slow and requires at least some manual work.

In the last couple of months - I've been doing all my tests with Docker. With Docker you can do something like this:


FROM ubuntu:latest

RUN apt-get install -y gdebi-core
ADD yourapp.deb var/yourapp.deb
RUN gdebi -n /var/yourapp.deb

# Various tests and checks
RUN /etc/init.d/yourapp-daemon status
RUN ls -lh /var/log/yourapp

CMD ["/bin/bash"]

If you have a lightweight package, it will run in less than 5 seconds. Even if you have a lot of dependencies, testing with Docker is really fast.

Conclusion

If you are a developer/devops, packaging your apps could solve a lot of issues in your deployment workflow - deploying your apps as self-contained native packages makes an update of an app a predictable operation. You can deploy on all your servers, regardless of the underlying distro specifics. Best of all - if something goes wrong, you can revert back by installing the older package version.

My personal experience is packaging Python applications and Python versions vary greatly between distros. With native packages I've been able to make a single installer including all the dependencies that just works.