Distribute your apps with apt

Debian’s app distribution (apt) is a simple, native, and friction-less tool to manage apps and their dependencies. Just with the combo apt update && apt upgrade you ensure that systems are up-to-date, including your software and its dependencies —and it works with Ubuntu and friends.

In this tutorial we learn how to package apps, generating .deb files, and distribute them through apt by creating a simple repository.

Create a debian package

A Debian package is a collection of files and folders compressed in a special way. Specifically it contains:

  • A folder called DEBIAN that contains:
    • A file called control, that has information about the package (like the name, the version…).
    • Optional scripts to execute during different stages of the installation.
  • Other files and folders that apt will just copy to the system where you are installing it. The root of the package mimics the root of the computer you are installing it, and anything you put in the package will be copied exactly. For example, if you want /etc/foo in your system you create a folder called etc in your package and add a file foo inside.

An example for a package is as follows:

|   |-- control
|   |-- postinst
`-- etc
    `-- my-program
        |-- LICENSE
        |-- config

In the package above we can see the required DEBIAN/control file, a postinst script (which we will analyze later), and then an etc/my-program/LICENSE and etc/my-program/config, two files that will be copied to the system, automatically creating the directory /etc/my-program in the process.

So, to create a package start with an empty folder and just add those files.

The DEBIAN/control file

The DEBIAN/control file includes metadata about the package. Items such as the package name, version number, and dependencies are specified here. An example with an explanation is as follows:

Package: mypkg
Version: 1.0-1
Architecture: all
Maintainer: My Org <hello@my.org>
Depends: foo,bar,baz
Description: my.org’s snazzy app
  1. Package: The name of the package. It must follow the Debian naming conventions.
  2. Version: The version of the package, explained below.
  3. Architecture: If your package contains compiled code, the architecture for this binary, otherwise all.
  4. Maintainer: An RCF 822 e-mail address.
  5. Depends: A comma-separated list of packages that are dependencies, which apt installs before installing your software.
  6. Description: A free-form, human-readable text.

Get here more information and options for the control file.


The version number is a sensible part in the package, as a wrong value can make apt to upgrade the package to an older version, which is difficult to debug and fix later. The version number must follow Debian versioning conventions.

However, such convention is quite open, so we have had better results by following the more restrictive version scheme of the Debian official repositories, simplified as <software version>-<debian revision>. A 1.3-2 means that is the software version 1.3 and the version 2 of the specific installation files and scripts. A 1.3-3 means that we changed the installation scripts and files, but it is still based on the 1.3 version of the software.

You can compare versions and check their correctness with dpkg --compare-versions <v1> <comparator> <v2>; echo $?, outputting 0 if it is True. The comparators are lt le eq ne ge gt. An example is dpkg --compare-versions 1.1-1 lt 1.1-2; echo $?. You can get more info in the man page of dpkg (look for –compare-versions).

Finally, the following example illustrates the hierarchy of versions: 1.9 < 1.12, 1.1~b1 < 1.1, 1.1 < 1.1-1 < 1.1-2.

Mantainer scripts

As we saw above, we can add scripts files in the DEBIAN folder. We can have up to four script files, called preinst, postinst, prerm, and postrm, representing the stage of the installation when the script is executed.

  • preinst executes before unpacking the files.
  • postinst executes after unpacking the files.
  • prerm executes before deleting the files when uninstalling.
  • postrm executes after removing the files when uninstalling.

Maintainer scripts are regular shell scripts with the following characteristics:

  • They must be idempotent, as they can be called several times. Because of it, add guards and prefer operations that are already idempotent (such as chown, systemctl enable|disable)
  • They must detect errors and propagate them upwards. If the script cannot perform its tasks, it has to notify the package system via its return code. In practice, we just add set -e at the beginning of the script.
  • They can be written in any scripting language by using a shebang, as long as you ensure that such interpreter is available (see pre-depends).

An example of a script is as follows:


systemctl disable bar
systemctl enable foo

if ! grep "mypkg-guard" /etc/NetworkManager/NetworkManager.conf; then
  cat <<EOF >>/etc/NetworkManager/NetworkManager.conf


Here you have more information.

Building the .deb

Once you have the folders and files of your future package, build it by executing dpkg-deb -b <path/to/source/dir> <destination/dir>. This results in a .deb file called package_version_arch.deb. You can install it with apt install <deb-file.deb>.

Setting up an apt repository

A Debian repository is an HTTP server that allows to access the .deb files in a regular folder of your server machine. This folder includes too a special Packages.gz file that is an index of the packages your repository offers, which apt gets when performing apt update.

For this example we use Apache to setup the repository.

1. Install apache with apt install apache2.
2. Create a file in /etc/apache2/sites-available/packages.my.org.conf with the following contents:

<VirtualHost *:80>
        ServerName localhost
        DocumentRoot /path/to/repo
        <Directory /path/to/repo>
        # We want the user to be able to browse the directory manually
        Options Indexes FollowSymLinks Multiviews
        Require all granted

/path/to/repo is the path you want your repository files to be in. An example is /home/<user>/repository/.

3. Give the apache user access to the folder. A quick way is by adding apache to the user group that created the folder: adduser www-data my-user.
4. Enable the site: a2enmod packages.my.org.
5. Restart apache: apachectl restart.
6. Go to the URL of your repository with a browser to see an empty directory. Otherwise you should fix your configuration.
7. Move the .deb files you built before to /path/to/repo, and do not change the name dpkg-deb gives them.
8. Run dpkg-scanpackages --multiversion -- /path/to/repo | gzip -9c > /path/to/repo/Packages.gz, which generates a suitable index file for apt update.

Redo step 6 and 7 every time you add new packages.

Now, in every computer you want to install packages from the repository, add the following line, as usual, to /etc/apt/sources.list: deb http://url.to.my.repo /.

apt install / upgrade will fail because the repository is not signed. Use apt-get install / upgrade instead and ignore the warning. Signed repositories are more difficult to create and, thus, are not part of this introductory tutorial. I would appreciate a guide or tutorial that explains it so I can link them here, or how to make apt work with unsigned repositories.

Happy coding 🙂

The original post was made by Daniel A. and it has been updated since by Xavier B.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.