In this guide I will show you how to make a basic Debian package, to redistribute your binaries. I will not, however, show you how to upstream or distribute a package through a repo as that’s a topic on its own. You will be able to share this .deb with your users/customers/friends for them to install. While you could do that through your website, it’s encouraged that you use a repo instead. If you don’t have a repo, you might want to check out Launchpad.

So, what’s a Debian package?

You probably already know, which is why you’re here but as a recap: At its core, it’s an installable archive whose name ends in .deb, that contains all the files you want to distribute to a Debian-based system, together with meta-data about the package (name, version, dependencies etc.).

Note that a Debian package also works for Ubuntu, as long as the dependencies are met. More on that later.

Basic package structure

Let’s say we have an application (binary), called helloworld that we want to be installed to /usr/bin, our basic Debian package structure would look something like:

DEBIAN/control
usr/bin/helloworld

But let’s not get ahead of ourselves, let’s just make and package helloworld right now as example!

Making helloworld

First, let’s make a simple helloworld application in C:

helloworld.c:

#include <stdio.h>

int main(void)
{
  printf("Hello World!\nTutorial by www.sindastra.de\n");
  return 0;
}

Which we will compile with:

gcc -O3 helloworld.c -o helloworld

Note that we did not “statically link” this, which is something we’ll talk about later.

And let’s verify it works:

./helloworld 
Hello World!
Tutorial by www.sindastra.de

Great! Let’s package it.

Packaging helloworld

Now let’s create our direcotry (folder) and file structure locally, assuming this is helloworld version 1.0:

mkdir -p helloworld-1.0/DEBIAN
mkdir -p helloworld-1.0/usr/local/bin
touch helloworld-1.0/DEBIAN/control
mv helloworld helloworld-1.0/usr/local/bin/

Notice how we used /usr/local/bin? That’s because we’re making a “local” package and not upstreaming or distributing it. We’re just playing around and don’t want to mess with /usr/bin for now!

Next, we’ll have to create the metadata, which we’ll do by editing the “control” file with your favourite editor. In this case, I’ll use nano:

nano helloworld-1.0/DEBIAN/control

And we’ll have to insert this template:

Package: 
Version: 
Architecture: 
Maintainer: 
Depends: 
Installed-Size: 
Homepage: 
Description: 

Let’s go through it line by line:

  • Package: This is the package name, in this case helloworld.
  • Version: As the name suggests, we’ll go for 1.0 here.
  • Architecture: Now this is an interesting part, if you’re making something architecture independent (for example a script) you can insert “all” here, otherwise you have to specify the architecture, but there’s something to note here: While “uname -p” might return the architecture “x86_64”, that architecture is actually called “amd64” in Debian packages.
  • Maintainer: Here you’ll specify your full name, together with an email address where you’re reachable at. Maybe create a separate email for this that forwards to your private email.
  • Depends: This is important, all the dependencies required to run your application. Unlike in the Windows world, we generally don’t statically link, and we don’t ship libraries with our application. Which in this case means, we’ll have to specify the standard C library as dependency. On Ubuntu 20.04 (and Debian 10 I believe) this would be “libc6”.
  • Installed-Size: Specify the size the package will need when installed, in KB.
  • Homepage: Specify the package’s homepage here, if you have none, specify the application’s GitHub page or repo or something that can be reached over the web!
  • Description: Here you’ll enter a short description. Note that on the next line, starting with one space, you can enter a long description.

The result is something like:

Package: helloworld
Version: 1.0
Architecture: amd64
Maintainer: Sindastra <noreply@localhost>
Depends: libc6
Installed-Size: 20
Homepage: https://github.com/sindastra
Description: Example helloworld
 This is an example application named helloworld for a Debian packaging tutorial.

Now we need to change the permissions and ownership, as this will be preserved. We’ll want to make it owned by root like this:

sudo chown -R 0:0 helloworld-1.0/

And now we’ll package it:

dpkg -b helloworld-1.0/
dpkg-deb: building package 'helloworld' in 'helloworld-1.0.deb'.

Let’s verify we do not have helloworld, then install it, and veriy that we then do:

helloworld

Command 'helloworld' not found

So, we’ll install it:

sudo dpkg -i helloworld-1.0.deb 

And try again:

helloworld 
Hello World!
Tutorial by www.sindastra.de

And it works! We can also list it with:

apt list --installed | grep helloworld

helloworld/now 1.0 amd64 [installed,local]

And we can remove it with:

sudo apt remove helloworld

And you’ll see that it’s gone when you try running it again.

If you’re confident that it works the way you want, and you want to distribute it, you should change the path from /usr/local to /usr so it doesn’t clash with “local” packages.

You should note that it’s possible to create install, upgrade and removal scripts that get executed when installing, upgrading or removing your package. You should also note that it’s possible to add even more metadata, like for example, specify the package’s license. But this is beyond the scope of this introductory guide. And you won’t need them for something as simple as this.

Other than that, that’s all there is to it!