At work, we use exclusively Debian across our fleet. We try to keep up with the latest stable distribution, without relying on third-party repositories. Also, we try to package every piece of software we’re installing on our boxes, including our IaaS cloud software Synnefo and tools that are not found in upstream Debian repos (i.e. check_mk). So, it is not uncommon that we have to create a Debian deb package from scratch.
After dealing with some custom packages, I tried to create and build a Debian
package without using any tools, except
The following is surely not the recommended way to create a package from
scratch, but I was quite curious about the internals and decided to dig a bit
deeper. Everything was done on a jessie box.
Before starting you’ll need to install some packages:
apt install dpkg-deb devscripts build-essential
First of all, I cd’ed into the repo of the software that I want to package. In
our case, it’s a dummy repo with some Markdown files that will end up in
cd path/to/repo and I created a tarball of my repo, which ideally should be
your ‘upstream’ tarball:
git archive -o /tmp/deb/koko_1.0.orig.tar.gz HEAD
I’m going to assume that the upstream version of our package is 1.0.
Our git tree looks like this:
$ tree . . └── lala ├── file0.html ├── file1.html └── file2.html 1 directory, 3 files
At work, we use a builder VM with multiple chroots, where all packaging is being done. So, let’s copy the “upstream” tarball there and ssh in.
scp /tmp/deb/koko_1.0.orig.tar.gz our.fancy.builder.vm: ssh our.fancy.builder.vm
Now, we need a directory where all package files will live. This directory must
have a name of
and now we’re going to extract the tarball in there:
tar xfv koko_1.0.tar.gz -C koko-1.0
All stuff needed for Debian’s tools to finally build the package, live inside the debian directory:
cd koko-1.0 mkdir debian
All packages, have a
debian/compat file, which contains the minimum supported
version of debhelper. Let’s set it to 9 (jessie ships version 9).
echo 9 > debian/compat
Of course, all packages contain a
debian/control file. This file contains the
most vital information about the source package: Name, priority, maintainer,
dependencies, descriptions. This info is also shown when you run
show [package]. See here for more info about control files and what each line
cat debian/control Source: koko Section: misc Priority: optional Maintainer: Nikos Kormpakis <email@example.com> Build-Depends: debhelper (>= 9) Standards-Version: 3.9.6 Package: koko Architecture: all Description: A dummy package. A dummy package, created for learning purposes, that just installs some files inside /opt.
Now, we have to define the format of our source package. Our package will be a
non-native Debian package. See here for the difference between native and
non-native packages. The package format is defined in
We’ll use version 3.0, the latest one.
mkdir debian/source echo "3.0 (quilt)" > debian/source/format
Let’s add our first entry to our changelog at
debian/changelog. In this file
we will define our package version number and describe what changed in this
revision. This file will be shipped to the machine that will install the
/usr/share/doc/koko/changelog.Debian.gz. Debian provides us a
dch(1), which eases us with the edit of the changelog file. If
you prefer, you can also create manually the file:
dch --create -v 1.0-1 --package koko
The file looks like this:
koko (1.0-1) jessie; urgency=medium * Initial release. -- Nikos Kormpakis <firstname.lastname@example.org> Sun, 23 Jul 2017 21:43:37 +0300
But why did we use version
1.0-1 instead of
1.0? Remember when we talked about
native and non-native packages? Debian Policy defines a standard way to define
a version of a package. As stated in Section 5.6.12 for more) the format of the
version string is:
The format is: [epoch:]upstream_version[-debian_revision]
The tl;dr about versioning is (ignoring epoch for now):
If you’re building a non-native package (99% of the time) you must define a
debian_revision starts from
-1 and increases by one
each time a new version of the package is releases, without changing the
upstream tarball. If a new upstream tarball is imported,
started again from
-1. So, must be clear now why we used
1.0-1 instead of
1.0 would imply that we’re building a native package.
After all that fuzz, let’s define what directories must be created by our package:
echo "opt/foo" > debian/koko.dirs
This means that during install, the package will create
and install our files inside
Now, let’s define which files will be copied during installation:
echo "lala opt/foo" > debian/koko.install
The above means that install everything you will find inside source directory
Last (but not least!) step: We need a
debian/rules file. This file, (which is
a Makefile) will be used by
dpkg-buildpackage(1) to create the actually
package. For now, we’ll use a bare simple rules file. For more gory details,
check Debian’s Maintainer Guide, Section 4.4.
#!/usr/bin/make -f %: dh $@
Please do not use spaces but only tabs.
dpkg-buildpackage(1) will complain!
debian/rules must be marked as executable:
chmod +x debian/rules
Now, we’re ready to build our package!
dpkg-buildpackage -sa -us -uc dpkg-buildpackage: source package koko dpkg-buildpackage: source version 1.0-1 dpkg-buildpackage: source distribution jessie dpkg-buildpackage: source changed by Nikos Kormpakis <email@example.com> dpkg-buildpackage: host architecture amd64 dpkg-source --before-build koko-1.0 debian/rules clean dh clean dh_testdir dh_auto_clean dh_clean dpkg-source -b koko-1.0 dpkg-source: info: using source format `3.0 (quilt)' dpkg-source: info: building koko using existing ./koko_1.0.orig.tar.gz dpkg-source: info: building koko in koko_1.0-1.debian.tar.xz dpkg-source: info: building koko in koko_1.0-1.dsc debian/rules build dh build dh_testdir dh_auto_configure dh_auto_build dh_auto_test debian/rules binary dh binary dh_testroot dh_prep dh_installdirs dh_auto_install dh_install dh_installdocs dh_installchangelogs dh_perl dh_link dh_compress dh_fixperms dh_installdeb dh_gencontrol dh_md5sums dh_builddeb dpkg-deb: building package `koko' in `../koko_1.0-1_all.deb'. dpkg-genchanges -sa >../koko_1.0-1_amd64.changes dpkg-genchanges: including full source code in upload dpkg-source --after-build koko-1.0 dpkg-buildpackage: full upload (original source is included)
After all that stuff, your directory must look something like this:
ls -l total 732 drwxr-xr-x 4 nkorb nkorb 4096 Jul 23 23:12 koko-1.0 -rw-r--r-- 1 nkorb nkorb 748 Jul 23 23:12 koko_1.0-1.debian.tar.xz -rw-r--r-- 1 nkorb nkorb 854 Jul 23 23:12 koko_1.0-1.dsc -rw-r--r-- 1 nkorb nkorb 359668 Jul 23 23:12 koko_1.0-1_all.deb -rw-r--r-- 1 nkorb nkorb 1584 Jul 23 23:12 koko_1.0-1_amd64.changes -rw-r--r-- 1 nkorb nkorb 360569 Jul 23 23:12 koko_1.0.orig.tar.gz
So, let’s install the package we built.
# dpkg -i koko_1.0-1_all.deb Selecting previously unselected package koko. (Reading database ... 295273 files and directories currently installed.) Preparing to unpack koko_1.0-1_all.deb ... Unpacking koko (1.0-1) ... Setting up koko (1.0-1) ... tree /opt/foo /opt/foo └── lala ├── file0.html ├── file1.html └── file2.html 1 directory, 3 files
Exactly as we expected! :) Many credits to the writers of the Debian Packaging Intro!
Finally, there are some links about Debian packaging, if you’re interested:
Debian New Maintainers’ Guide
Debian Packaging Tutorial by Lucas Nussbaum
Have fun packaging!