Building a very simple debian package

I sometimes read "building packages X is much easier than building .debs".

While it is probably true that building a debian package is a little more involved than other systems, the debian packaging system adds a lot of value for end users, and properly packaged applications is what makes the Debian and Ubuntu ecosystems extremely powerful.

A package

This blog post will guide you through making a very simple debian package from scratch, that simply "copies" files (a wallpaper, for example) where you specify them on the target file system. That is of course not how most packages in the Debian & Ubuntu archives are created since it skips a lot of the necessary subtleties, but it is hopefully a good first step to build further articles upon.


# Prerequisites
apt install devscripts
# Packaging
mkdir $package-$version
cd $package-$version
dh_make --indep --createorig
mkdir essentials
mv debian/{changelog,compat,rules,control} essentials
rm -r debian
mv essentials debian
# edit debian/control
mkdir tree
echo './tree/* ./' > debian/$name.install
# Add files to tree/ as if /tree was / on target system
debuild -S
dput $ppa_address ../*.changes

Anatomy of a package tree

In a nutshell, a debian package tree is:

  • an untarred source bundle of the upstream program's source code.
  • a debian/ subdirectory that the packager/maintainer adds to it. This subdirectory contains all the information needed to build the surrounding program.

Generating a package skeleton with dh_make

dh_make is a handy program that will generate a debian/ subdirectory for you, a bit like paster would do for ruby on rails projects. It asks for questions interactively, but for such a simple project, we don't need most of its features. As usual in Ubuntu land, it all starts with:

# That pulls way more than you need, poke around!
sudo apt install devscripts

Start by picking a package name, and a version you want you package to be. I'll go with the class "foo" and "0.1", but feel free to chose something more relevant:

mkdir foo-0.1
cd foo-0.1
# Here we pick "indep" to specify that the package is architecture independant.
dh_make --indep --createorig

This will have created a debian/ subdirectory in you foo-0.1 directory. You can have a peek inside, but again, we won't need most of it for our simple example. Let's narrow it down to the essentials:

mkdir essentials
mv debian/{changelog,compat,rules,control} essentials
rm -r debian
mv essentials debian

Our little gymnastic here recreated the debian/ subdirectory, this time with only the most important files present.

You folder should now look like:

└── debian
    ├── changelog
    ├── compat
    ├── control
    └── rules

Filing the generated control file

The control file is the file that lists all the package's metadata, and, roughly everything that will show up for your users when they use apt-cache to query information about it.

While the generated control file is almost useable as-is, we'll need to quickly edit a couple of fields for our new package:

Choosing a Section

Sections are used to group packages related to a particular topic. You should pick one from the list .

For a wallpaper package the "universe/graphics" section is probably appropriate.

This is optional however, so you as well just remove the "Section:" line for our dead simple package.

Editing the homepage

Either add a link to a relevant website, or remove the line altogether.

Adding a source control link

If you are planning to maintain this package with a version control system (which is a pretty good idea in general), you should add the URL to it in the control file, as modern dh_make will have pre-filled for you.

Users of your package will get a warning message when getting the package's source (using "apt-get source" for example) telling them where the package is maintained, and will therefore help potential contributors know how and where to submit packaging fixes.

Adding description fields

The most important part of this exercise: add a short and long description of what your package does.

Adding the actual files

It's time to add the files we want to ship. For this, let's prepare the ground and add a "tree" directory in our foo-0.1 folder. That folder will be "translated" into our target system's root by the install rules we'll create just now:

mkdir tree
echo './tree/* ./' > debian/foo.install

Make sure to replace "foo.install" by ".install"!

You can now add your files in the "tree" subdirectory, as if it was the target system's root.

For example, adding a wallpaper to a desktop install:

mkdir -p tree/usr/share/backgrounds/
cp my_penguin_picture.png tree/usr/share/backgrounds/.

On the installed system, the penguin picture file will be installed in /usr/share/backgrounds

Your tree should now look like something like this:

├── debian
│   ├── changelog
│   ├── compat
│   ├── control
│   ├── rules
│   └── foo.install
└── tree
    └── usr
        └── share
            └── backgrounds
                └── my_penguin_picture.png

Create a changelog entry

Time to add a changelog entry to your package.

The Debian changelog is the file that tracks what changes in your package, in a machine-readable way, and is also the file that determines what the version number for particular build will be.

The "dch" program will help you greatly in this task by doing most of the grunt work for you. If your package were a "real" package to be distributed with Ubuntu proper, you'd need to pay special attention to the version number, but for now, we'll just go for the easy scheme and number things like "0.1" then "0.2" and so on.

Doing things this way is called creating a "native package".

Here's what my example changelog looks like after editing it:

foo (0.1) zesty; urgency=low

* Initial release. Add whatever is relevant here.

-- Christopher Glass <>  Thu, 23 Jul 2015 18:26:05 +0300

Make sure your name matches your GPG key's identity exactly here, that will make signing the packages more simple.

Important: Make sure you replace the default "unstable" by whatever Ubuntu version you target! It's a classic source of failing builds (at least for me).

Building and uploading the package

Making a test binary deb

Before we think about publishing our work, let's build a test .deb file. Simply running the following command in your top-level directory should take care of the deb file creation:


debuild will ask you for your GPG key's passphrase, since packages are always signed.

It will leave a fresh new debian package one directory up from your foo folder. You can copy this to a test system (I suggest a LXD container), and install it there, for example with gdebi or dpkg.

Making and uploading a source deb to a PPA

Once you are happy with you test deb, it's time to think about publishing your work (if you want to)!

The easiest way to do this in the Ubuntu world is to create and use a free PPA (Personal Packages Archive) on Launchpad. In your user account simply create a new PPA, and note down the name you give it.

The "create a PPA" button

From there, you simply need to invoke the following command from your top level foo directory to build a source package, suitable for upload to Launchpad:

# The -S stands for "source" here. Build a source package.
debuild -S

That will build the source package for you to upload to a PPA, and as for the binary package, all the needed files will be stored one directory up from your top-level directory.

PPAs do not accept binary packages, and will build the binary packages from that source package for you. While it is trivial for this example, it is a very nice feature for compiled programs since the Launchpad build farm will build your packages for many different architectures for you automatically!

As for the upload itself: simply move to one level above your work directory (where the build artifacts are put), and invoke the following command:

dput ppa:user-name/ppa-name name_of_your_package_0.1_source.changes

You will receive an email at your main launchpad email address telling you that your package was received, and that it (hopefully) build successfully!

You can then install your package by adding the PPA to your machine and installing it using apt, as you'd expect.

sudo add-apt-repository --update ppa:user-name/ppa-name
sudo apt install name_of_your_package

Next up

Next in this series of posts, I'll guide you through making a simple python package, that should hopefully be more "shippable", and how to do equivalent things using snaps!


You can comment on this article on Reddit.