- Quick Debian development guide
- Find the source
- Modifying the package
- Building the package
- Testing packages
- Uploading packages
- Further work and remaining issues
This guides aims to kickstart people with working in existing Debian packages, either to backport software, patch existing packages or work on security issues as part of the security team or the LTS project.
This guide assumes that the Debian package already exists and you are making modifications to it. If you wish to make a new Debian package, there are 3 different guides that you can follow, and I am not going to create a fourth one (even this guide is duplicating existing efforts already). So go see one of those:
- Debian New Maintainer's Guide: the one I followed originally
- Guide for Debian Maintainers: a new version of the above, basically using the new debmake instead of dh-make, untested
- Introduction to Debian packaging: a set of slides, a good primer for people that like slides, although a bit technical
All those are part of the developer's manual suite which also includes the Debian policy and the Debian developer's reference, two more reference manuals which you may find useful when looking for more information.
This guides tries to take a streamlined and opinionated approach to
maintaining Debian packages. It doesn't try to cover all cases,
doesn't try to teach you about debhelper, cdbs, uscan or
make. It assumes you will find that information elsewhere, for
example in the above references, and that you are already somewhat
familiar with Debian systems administration (you know how to use a
shell) and Debian packages as a concept (you know what a
is and know how to use
This will guide you through a standardized approach to:
- download Debian package source code
- make modifications to packages
- build packages, especially with multiple environments (testing, unstable, backports)
- upload packages
In the following, I take the example of building a backport of the Calibre package, which I needed. It's a good example because it does not use a git repository to track the Debian package source code, but Bazaar, which I am not familiar enough with to feel comfortable working on.1
But if the version control system the package uses is familiar to you, you can use debcheckout to checkout the source directly. If you are comfortable with many revision control systems, this may be better for you in general. However, keep in mind that it does not ensure end-to-end cryptographic integrity like the following procedure does. It will be useful, however, if you want to review the source code history of the package to figure out where things come from.
So to get the source code on an arbitrary package, visit the
package tracker.2 In this case, we look at the
Calibre package tracker page and find the download links for the
release we're interested in. Since we are doing a backport, we use the
testing download link. If you are looking for an antique package,
you can also find download links on archive.debian.net.
debian: calibre | 0.7.7+dfsg-1squeeze1 | squeeze | source, all calibre | 0.8.51+dfsg1-0.1 | wheezy | source, all calibre | 1.22.0+dfsg1-1~bpo70+2 | wheezy-backports | source, all calibre | 2.5.0+dfsg-1 | jessie-kfreebsd | source, all calibre | 2.5.0+dfsg-1 | jessie | source, all calibre | 2.55.0+dfsg-1 | stretch | source, all calibre | 2.55.0+dfsg-1 | sid | source, all ubuntu: calibre | 0.8.38+dfsg-1 | precise/universe | source, all calibre | 1.25.0+dfsg-1build1 | trusty/universe | source, all calibre | 1.25.0+dfsg-1ubuntu1 | trusty-updates/universe | source, all calibre | 2.20.0+dfsg-1 | vivid/universe | source, all calibre | 2.33.0+dfsg-1build1 | wily/universe | source, all calibre | 2.55.0+dfsg-1 | xenial/universe | source, all calibre | 2.55.0+dfsg-1build1 | yakkety/universe | source, all
To get the Ubuntu results, I added the following line to my
What we are looking for is the calibre_2.55.0+dfsg-1.dsc file, the
"source description" file for the
2.55.0+dfsg-1 version that is
currently in stretch. Using that
.dsc file, we can get all we need
to build the package. So the first step is to download the source
code, using dget(1):
$ dget http://httpredir.debian.org/debian/pool/main/c/calibre/calibre_2.55.0+dfsg-1.dsc dget: retrieving http://httpredir.debian.org/debian/pool/main/c/calibre/calibre_2.55.0+dfsg-1.dsc % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0 100 2620 100 2620 0 0 1349 0 0:00:01 0:00:01 --:--:-- 36388 dget: retrieving http://httpredir.debian.org/debian/pool/main/c/calibre/calibre_2.55.0+dfsg.orig.tar.xz % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0 100 37.3M 100 37.3M 0 0 2931k 0 0:00:13 0:00:13 --:--:-- 3032k dget: retrieving http://httpredir.debian.org/debian/pool/main/c/calibre/calibre_2.55.0+dfsg-1.debian.tar.xz % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0 100 22800 100 22800 0 0 59423 0 --:--:-- --:--:-- --:--:-- 59423 calibre_2.55.0+dfsg-1.dsc: Good signature found validating calibre_2.55.0+dfsg.orig.tar.xz validating calibre_2.55.0+dfsg-1.debian.tar.xz All files validated successfully. dpkg-source: info: extraction de calibre dans calibre-2.55.0+dfsg dpkg-source: info: extraction de calibre_2.55.0+dfsg.orig.tar.xz dpkg-source: info: extraction de calibre_2.55.0+dfsg-1.debian.tar.xz dpkg-source: info: mise en place de no_updates_dialog.patch dpkg-source: info: mise en place de disable_plugins.py dpkg-source: info: mise en place de use-system-feedparser.patch dpkg-source: info: mise en place de python_multiarch_inc.patch dpkg-source: info: mise en place de dont_build_unrar_plugin.patch dpkg-source: info: mise en place de mips_no_build_threads.patch dpkg-source: info: mise en place de links-privacy.patch
A lot of stuff has happened here!
First, dget downloaded the
.dsc file, which includes references to
.orig.tar.xz file and the
debian.tar.xz files. The
.orig.tar.xz is the upstream source code,3 and the
.debian.tar.xz file is basically the content of the
subdirectory, the metadata used to build the debian package, including
all the patches specific to Debian.
Then dget downloads the files
Then the OpenPGP signature on the
.dsc file is verified against the
web of trust,4 using dscverify. The
.dsc files includes
checksums for the downloaded files, and those checksums are verified
Then the files are extracted using
dpkg-source -x. Notice how
is basically just a shortcut to commands you could all have ran by
hand. This is something useful to keep in mind to understand how this
At this point, we have a shiny source tree available in the
We can start looking around and make some changes.
The first thing we want to make sure we do is to bump the version
number so that we don't mistakenly build a new package with the same
version number but with undocumented changes. The Debian package
version is stored in
In the case of calibre, I only needed to change that file to complete
the backport. This is called a "trivial backport": we just need to
recompile against the
stable environment. In other cases,
dependencies need to be modified, patches need to be included and so
on. But let's stick with the version number for now.
The generate the new version, I use
dch --bpo, which chooses the
right version number for me and pops open my
$EDITOR so I can add
things to the changelog:
In this case, this created an entry for the
version5. I also added a
Closes entry to indicate that the
upload will fix a bug report I opened about creating that very
backport. It's important to describe exactly what you are doing in the
changelog and what bugs are being fixed. Here's the result:
calibre (2.55.0+dfsg-1~bpo8+1) jessie-backports; urgency=medium * Rebuild for jessie-backports (Closes: #818309) -- Antoine Beaupré <email@example.com> Tue, 26 Apr 2016 16:49:56 -0400
Note that there are other options you can pass to
dch. I often use:
--security, -sfor security uploads
--nmu, -nfor non-maintainer uploads
--increment, -ifor my own packages
If I needed to modify dependencies, I would have edited
debian/control directly. Other modifications to the Debian package
would also happen in the
debian/ directory. The function of the
various files in that directory vary a lot according to how the
package is built, but a good starting point is
Debian policy §4: Source packages.
If I needed to modify the source tree outside
debian/, I can do
the modifications directly, then use
dpkg-source --commit to
generate a patch that will end up added to the quilt patchset in
debian/patches. New patches should follow the
patch tagging guidelines and
dpkg-source --commit will use that
template when creating a new patch.
If I already have a patch I want to apply to the source tree, then quilt is even more important. The first step is to import the patch:
quilt import ~/patches/CVE-2015-8477.patch
The above simply adds the
CVE-2015-8477.patch to the patch set, but
does not apply. You can apply all patches with:
quilt push -a
Quilt may tell you that the patch fails to apply. You can try to force-apply it with a "fuzz" argument:
quilt push --fuzz 100
Note that if the patch fails to apply, quilt leaves the source tree intact, so you can try that again and again. If the above fails but you are confident you can manually fix the patch, you can force-apply the patch:
quilt push --force
... then apply the modifications by hand. Then you need to refresh the patch to make sure it is updated correctly:
Review the patch in
debian/patches to make sure it still looks sane.
Notice how you could also have bypassed
quilt completely and applied
the patch with the
patch command directly, then use
--commit to generate a completely new patch.
Also note that the above does not care where the patch comes from. I
often extract the patch from a Git source tree fetched with
debcheckout on the side, with, for example:
( cd ../source-git ; git show $hash ) > debian/patches/CVE-2015-8477.patch quilt import debian/patches/CVE-2015-8477.patch quilt push
Again, it's useful to add metadata to the patch and follow the patch tagging guidelines.
Now that we are satisfied with our modified package, we need to build
it. The generic command to build a Debian package is
dpkg-buildpackage. So at the root of the source tree
debian/ directory), simply run:
gbpin short) for that purpose, but other also use the simpler git-pkg. I find that
gbphas more error checking but it is more complicated and less intuitive if you actually know what you are doing, which wasn't my case when I started.7
In any case, there's a catch here. The catch is that you need all the build-dependencies for the above builds to succeed. You may not have all of those, so you can try to install them with:
sudo mk-build-deps -i -r calibre
But this installs a lot of cruft on your system!
a dummy package to wrap them all up together, so they are easy to
uninstall, but still: this is relevant only if you are doing recurring
development on the project.
Furthermore, it doesn't build the package in a "clean
environment". For example, say I am building a package for a regular
unstable ("sid"). Yet, my workstation is running
stable ("jessie", currently). I can't simply build the package in
jessie and expect it to work in
sid, I need to build it into a
For this, we need more powerful tools.
pbuilder takes your source package (the
.dsc file), and builds
it in a clean, temporary
chroot. To create that
.dsc file, you can
dpkg-source -b or simply call
pdebuild instead of
which will do that for you. Some places, like Debomatic, require a
.changes file, which is generated with
However, I don't use pbuilder in its default configuration as it uses tarballs, which are slow to create and extract. Instead, I use cowbuilder, which uses a clever hack to optimize the creation of the temporary chroot, using hardlinks and cowdancer.
To use cowbuilder, you first need to create the base images:
for DIST in wheezy jessie stretch sid; do for ARCH in i386 amd64; do sudo cowbuilder --create --distribution $DIST --architecture $ARCH --basepath=/var/cache/pbuilder/base-$DIST-$ARCH.cow done done
The above will create chroots for all the main suites and two
architectures, using debootstrap. You may of course modify this to
taste based on your requirements and available disk space. My
pbuilder directory is around 7GB (including ~3GB of cached
packages) and each chroot is between 500MB and 700MB.
It is also useful to configure your
.pbuilderrc to make all this
easier to use. Noticed how long the above cowbuilder commandline is? I
use a modified version of the Ubuntu pbuilder Howto
in my configuration that allows me to avoid having to pass
--basepath all the time, and
just set the
Then I build packages in one of three ways.
With cowbuilder, if I have a
.dscalready (again, that can be generated with
dpkg-source -bin the source tree):
DIST=jessie ARCH=amd64 cowbuilder --build calibre_2.55.0+dfsg-1~bpo8+1.dsc
With pdebuild if I'm in the source tree:
DIST=jessie ARCH=amd64 pdebuild --pbuilder cowbuilder
The above can be configured by default in
[DEFAULT] builder=/usr/bin/git-pbuilder # to force lintian to run since we don't use debuild anymore postbuild = lintian $GBP_CHANGES_FILE
All of those will generate your binary package in
To pass options to the underlying
dpkg-buildpackage (for example,
you often need
-sa to provide the source tarball with the upload),
you should use
-- --debbuildopts -sa in
cowbuilder. For git-buildpackage, simply add
-sa to the
Sometimes, your machine is too slow to build this stuff yourself. If
you have a more powerful machine lying around, you can use cowpoke
to send builds to that machine.
cowpoke operates on a source package
.dsc file created with
dpkg-source -b) and works well with
gitpkg. By default,
cowpoke logs into a remote server and uses
sudo to call
cowbuilder to build a chroot. For example, this will
build calibre on the remote host
buildd.example.com, in a
cowpoke --buildd buildd.example.com --dist sid --arch amd64 calibre_2.55.0+dfsg-1~bpo8+1.dsc
This assume the chroot already exists of course. You can create it by
--create argument. It also only works for
.dsc files, so
it doesn't cooperate well with git-buildpackage, which expects a
To build from git, you first use
gitpkg to generate a
from the git tree, where
rev is the current commit of the debian
package (can be
master or a specific tag) and
upstream is a
revision pointing at the upstream release to generate the
tarball if not already present:
gitpkg rev upstream
cowpoke on the resulting
If you do not have your own host to build packages, you can upload
source packages to another buildd using
dput, for example through
debomatic. You need to request access first, however. More
documentation is available on the Deb-o-Matic site.
In my experiments, Debomatic was way faster than compiling on my laptop, so I sometimes use it for larger packages. For example, building Xen on Debomatic takes around 6 minutes while on my laptop it takes almost triple that time (17 minutes). Plus Debomatic runs lintian and piuparts.
The obvious downside is that I need to trust the remote server to generate the same package as I would do locally. Even if the package is reproducible (which is not always the case!), I would still have to build the package locally to ensure the package was trustworthy.
Some packages have a built-in test suite which you should make sure runs properly during the build. Sometimes, backporting that test suite explicitly can be useful to ensure that everything works properly after a backport or LTS security upload. A key component of this is DEP8, currently implemented as autopkgtest. piuparts can also be used to see if the package cleans up properly after itself.
In some cases, however, those tests are not available or not sufficient and you need to actually install and run the package somewhere.
Backports can obviously be tested directly on your local machine if you are running stable (which is likely if you are building backports, unless you are doing them for someone else of course).
Otherwise, it is very likely that you will need to build a separate environment to test the package if it is built for another distribution. For this, you can use the debootstrap and chroot commands. But it is probably better to run tests within a completely isolated environment, often called a "Virtual Machine".
There are a lot of different virtualization solutions you can use (Xen, KVM, Docker and Virtualbox), which are for now considered to be outside the scope of this tutorial. I have also found libguestfs to be useful to operate on virtual images in various ways. Libvirt and Vagrant are also useful wrappers on top of the above systems.
Hashicorp's Vagrant is a useful shortcut you can use to build virtual machines consistently. You can get started by using the following commands to get SSH into a Wheezy machine, for example:
sudo apt install vagrant mkdir wheezy64; cd wheezy64 vagrant init debian/wheezy64 vagrant up vagrant ssh
And that's it.
vagrant init creates a Vagrantfile that basically
vagrant up can recreate the VM. You can
use shell commands, Ansible, Puppet
or other provisionning tools to automatically configure the VM, of
vagrant halt to stop the VM and
vagrant destroy to
actually remove all data associated with the VM.
Note that the directory where the VagrantFile is store is,
by default, shared with the virtual machine, in the
directory. This allows you to share files between the host and the VM,
and allow for files to "stick" when destroying the VM. This is
synchronized using vagrant rsync, see
the official Debian baseboxes documentation for more
For now, we will stick with the simplest approach which is using Qemu. We will need to use a special tool to create the virtual machine as debootstrap only creates a chroot, which virtual machines do not necessarily understand. Here is how to create the virtual machine disk using vmdebootstrap.
DIST=sid ARCH=amd64 sudo vmdebootstrap --serial-console --enable-dhcp --convert-qcow2 --verbose --distribution=$DIST --arch=$ARCH --image=$DIST-$ARCH.qcow2
This makes sure that the
--serial-console is enabled, which allows
us to connect to the VM without having a complete GUI. It also enables
--enable-dhcp). It also converts the resulting image to
the QCOW file format, which takes up less space as it expands as
necessary on writes.
There are issues when setting up a
wheezy machine on older
vmdebootstrap versions: for example, it couldn't setup the
bootloader. I had trouble with the jessie version (
0.5), and even
with backports (
1.4). Try to have at least 1.6 running. This may
also mean using newer
e2fsprogs from backports as well.
There is another tool that accomplished similar things called
grml-debootstrap. I do not use it because it doesn't create a
minimal image by default.
vmdebootstrap is also destined to be the
main tool used to create Debian Live official images which makes
it interesting in the long term. I have used the following commandline
when using grml-deboostrap:
sudo grml-deboostratp --vmfile --bootappend console=ttyS0 --arch $ARCH --release $DIST --target $DIST.qcow2
To boot those images with Qemu, use:
qemu-system-x86_64 -snapshot -enable-kvm -display none -serial mon:stdio $DIST-$ARCH.qcow2
-snapshot makes the image read-only, so it can be readily reused
without worring about contaminating the environment. KVM is
obviously optional here, but usually works in my tests and is much
faster than non-HVM usage. The remaining options are to make sure
I get a regular terminal from Qemu instead of a graphical window. This
requires special configuration in the image, otherwise you will get no
output at all. Also, if you are testing GUIs, you will obviously want
to remove those options and install a bunch of packages on top of the
To transfer data between the host and the virtual machines, the simplest way I could find is with netcat. On the host:
nc -q 0 -l -p 10080 < /var/cache/pbuilder/wheezy-amd64/result/phpmyadmin_184.108.40.206-2+deb7u4_all.deb
In the VM:
nc 10.0.2.2 10080 > phpmyadmin_220.127.116.11-2+deb7u4_all.deb
The IP address may change, use,
ip route to find the address of the
host, which should be the gateway.
10080 is an arbitrary port above
Ports can also be forwarded from the host to the VM using the
command. For example,
-net user,hostfwd=tcp::10022-:22 -net nic
would allow the host to connect to the VM's SSH server. I ended up
setting up the following shell alias:
# qemu: specify architecture, enable serial port and common port # forwards (HTTP and SSH), enable KVM support and don't write the # image by default (can be worked around with C-a s at # runtime). graphical display still enabled for POLA (some VMs don't # have serial), can be turned off with -display none. alias qemu="qemu-system-x86_64 -serial mon:stdio -net user,hostfwd=tcp::10080-:80 -net user,hostfwd=tcp::10022-:22 -net nic -enable-kvm -snapshot"
.deb files can be installed with
dpkg -i, which will likely fail
because of missing dependency, so you need to also run
Another option is to use the "unshare" command, which launches another command in a different namespace:
sudo unshare -i -m -p -u -f chroot /path/mountpoint qemu-arm-static /bin/bash
This can reuse chroots previously created for cowbuilder, but the filesystem separation works only if /path/mountpoint is really a different mountpoint. Otherwise changes in the filesystem affect the parent host, in which case you can just copy over the chroot.
Uploading packages can be done on your own personal archive if you
have a webserver, using the following
[people] fqdn = people.debian.org method = scp incoming = /home/anarcat/public_html/debian/wheezy-lts/ run_dinstall = 0 progress_indicator = 2
The above archive is not usable in a
sources.list file, because that
would involve an incoming queue and all sorts of complicated
things. Instead, people are expected to use
dget to download the
.changes file and check the signatures. See the HEADER.html on my
personal archive for an example.
The pbuilder configuration should probably be factored into the default pbuilder configuration.
I am curious about Whalebuilder, which uses Docker to build
packages instead of
pbuilder. Docker provides more isolation than a
whalebuilder, packages are built without network
access and inside a virtualized environment. Keep in mind there are
limitations to Docker's security and that
pbuilder does build
under a different user which will limit the security issues with
building untrusted packages. Furthermore,
whalebuilder is not
currently packaged as an official Debian package and lacks certain
features (like passing custom arguments to dpkg-buildpackage) so I
don't feel it is quite ready yet. For now, if you need better
isolation, look towards qemubuilder or possibly kvmtool.
Apparently, I should also look at sbuild again. AskUbuntu.com has a good comparative between pbuilder and sbuild that shows they are pretty similar. The big advantage of sbuild is that it is the tool in use on the buildds and it's written in Perl instead of shell. My concerns about switching are POLA, the fact that pbuilder runs as a separate user (needs to be checked in sbuild), and setting up COW semantics in sbuild (can't just plug cowbuilder there, need to configure overlayfs or something similar). Ubuntu folks, again, have more documentation there. Debian also has extensive documentation, especially about how to configure overlays.
Cruft creeps up in the
directories, and there is no clear way of purging that automatically.
apt-get -o Dir::Cache::archives=/var/cache/pbuilder/$dist-$arch/aptcache/ -o Dir=/var/cache/pbuilder/$dist-$arch.cow/ autoclean could be a solution.)
Well, it's not exactly true: I know bzr well enough, but I'm lazy. Besides, the point is to have a procedure that works regardless of the version control used. Furthermore, some packages do not use version control at all! ↩
we use the tracker instead of
dget package=versionbecause those rely on a
sources.listwith all possible distributions enabled, something that is really inconvenient and harder to configure and document. ↩
Well, not exactly: in this case, it's a modification the upstream source code, prepared specifically to remove non-free software, hence the
+dfsgsuffix, which is an acronym for Debian Free Software Guidelines. The
+dfsgis simply a naming convention used to designated such modified tarballs. ↩
In my case, this works cleanly, but that is only because the key is known on my system.
dgetactually offloads that work to
dscverifywhich looks into the official keyrings in the debian-keyring package. This package may be missing some keys because the maintainers are new and were not in the keyring when the Debian version you are using was released. In this case, you need to find the key yourself, add it to your keyring, and then adding the following to
~/.devscriptswill leverage your personal keys into the web of trust:
You can also use
dscvrify --keyring key.gpg *.dscto check the signature by hand against a given key file. ↩
The "tilde" character to indicate that this is a lower version than the version I am backporting from, so that when the users eventually upgrade to the next stable (
stretch, in this case), they will actually upgrade to the real version in stretch, and not keep the backport lying around. There is a detailed algorithm description of how version number are compared, which you can test using
dpkg --compare-versionsif you are unsure. ↩
.changesfile is similar to the
.dscfile, but also covers the
.debfile. So it's the
.dscfile for the binary package. [^debuild]: I also often use
dpkg-buildpackagebecause it also runs lintian and signs the binary package with debsign. ↩
git-pkg actually only extracts a source package from your git tree, and nothing else. There are hooks to trigger builds and so on, but it's basically expected that you do that yourself, and gitpkg is just there to clean things up for your. git-buildpackage does way more stuff, which can be confusing for people more familiar with the Debian toolchain. ↩