How to create a Debian package for Ubuntu, Pop!_OS, Arch, etc.

How to create a Debian package for Ubuntu, Pop!_OS, Arch, etc.
Packaging for Debian Linux

Debian Linux is one of (if not the) most popular Linux distribution base, with distros such as Ubuntu, Pop!_OS, and Arch being built on them. There's many different tools associated with building debian packages, which can make it challenging to know where to start when first attempting to create a package. 2 main tools suited for this task are dpkg-deb and debuild.

dpkg-deb

dpkg-deb is great tool to get started with when creating debian packages, an all that's required is creating a folder with your package assets and a debian directory defining the control and changelog files. This makes it easy to get started, but as soon as you want to do something more complex this quickly becomes too simple. Enter debuild.

debuild

debuild is actually a wrapper over a suite of different programs used to generate debian packages that are meant to be distributed publicly. It handles a lot of the package building process for us so we don't have to run multiple commands to build a package. This tool requires a few files within a debian folder within your project files.

debian/control file

The debian/control file is required for all debian packages as it defines metadata about the package being built. For example:

Source: my-awesome-package
Priority: optional
Maintainer: Ryan Mellmer

Homepage: foobar.com
Depends: docker.io (>= 21.00.0)
Package: my-awesome-package
Architecture: amd64
Description: Ryan's awesome new package

This file is specifying info for a new package called my-awesome-package, shows who authored it, and a description for the package.

Dependencies in the control file

You'll also notice a "Depends" section, where we can define which packages need to exist for this package to work, including any specific version requirements for those packages. Here, we're defining that docker.io version >= 21.00.0 is required.

When apt install is ran to install our package, apt will attempt to also install any missing dependencies for the package. Note that apt will not by default ever install a lower version number of a package than what is the latest, so if you have specified a specific dependency version with a = (i.e. docker.io (= 21.00.0), notice the missing >), then apt will only install that package for you if 21.00.0 is indeed the latest version in the repo. Otherwise, you'll get this error:

The following packages have unmet dependencies:
 my-awesome-package : Depends: docker.io (= 21.00.0) but 23.10.0 is to be installed

This means that the package requires version 21.00.0, but apt will only install the latest, which is 23.10.0.

There are two ways around this at install time:

  1. Install the packages manually (i.e. sudo apt install docker.io=21.00.0) before installing your new package.
  2. Pin the dependency versions in a new file within /etc/apt/preferences.d/*. An example file continuing with the example from above could be:
Package: docker.io
Pin: version 21.00.0
Pin-Priority: 999

The existence of this file tells apt to always prefer installing version 21.00.0 of the docker.io package instead of whatever the latest is.

debian/rules file

The debian/rules file is a Makefile that defines how the package will be built. debuild will call various make targets within in this file during the build process.

The most simple debian/rules file could look something like:

#!/usr/bin/make -f

%:
	dh $@

This rules file will just pass all make targets called by debuild over to the dh (debhelper) equivalent. Various steps within the dh build process can be captured and overridden within the rules file.

Overriding dh build commands in the rules file

As mentioned previously, we can override functionality of various dh commands in our debian/rules file. For example, if we wanted to override the dh_auto_install step (which defines how files get added or installed to the package at build time), we can add the following to the rules file:

#!/usr/bin/make -f

PKG_DIR = debian/my-awesome-package

%:
	dh $@

# Define which files to install, paths relative to ..
override_dh_auto_install:
	mkdir -p ${PKG_DIR}/root/
    install -m 750 ./my-script.sh ${PKG_DIR}/root/my-script.sh
...

This override_dh_auto_install step will include a shell script called my-script.sh to the /root/my-script.sh directory within our package directory. When the resulting debian package is installed, this script will be installed on the system at /root/my-script.sh.

Inspect files within .deb package

After building a deb package via debuild, the dpkg-deb command can be used to get a list of all files included in the package:

> dpkg-deb -c my-awesome-package.deb

...
drwxr-xr-x root/root         0 2023-02-02 19:06 ./root/
-rwxr-xr-x root/root      7136 2023-02-02 19:06 ./root/my-script.sh
...

Run debuild without signing

By default, debuild requires a GPG key for signing your package. However, for local use and testing, you can run debuild without signing the package by running:

debuild -b -uc -us

Generate a GPG key for your package

Eventually you'll probably want to sign your package with a gpg key if you want to publish it. You can generate a new key locally by running:

gpg --generate-key

This will prompt you for a "real name" and an email address. Email address can be left blank if desired. Then, will prompt for a passphrase to protect the key with.

How to pass Environment Variables into debian/rules

By default, debuild sanitizes environment variables when running the build process (see this link. However, you can still pass specific environment variables through and used throughout the debian/rules file using the -e flag. For example, when calling debuild:

debuild -e VERSION=1.0.0

The VERSION variable will be available for use in debian/rules, and can be used like any other Makefile environment variable. In the following example, we override dh_gencontrol in the rules file to pass the VERSION variable to the debian/control file:

#!/usr/bin/make -f
# VERSION=x.x.x debuild 

%:
	dh $@

# Set the Version: in DEBIAN/control with specified version
override_dh_gencontrol:
	dh_gencontrol -- -v$(VERSION)

Finally, we modify the control file to use this new version variable:

Source: my-new-package
Priority: optional
Maintainer: Ryan Mellmer

Version: ${version}
Homepage: foobar.com
Package: my-new-package
Architecture: amd64
Description: Ryan's awesome new package

Cleaning up

The dh_clean can be used to clean up local build artifacts after debuild has been ran.

Where to go from here?

While I hope this is a good place to start tinkering with debian packaging, there's tons more for me to learn. The official debian docs are a good place to dive deeper into building debian packages.