This tutorial will cover how to create a debian (.deb) package, which just prints one line when executed. This package is then hostet on an apache2-based repository.

Prerequisites: Install following packages:

sudo apt-get install -y gcc dpkg-dev gpg curl apt-utils

Firstly create directory ~/build/:

mkdir -p ~/build/firstpkg/

Enter the directory and create a file called main.c with the following content. You can use any editor therefore.

#include <stdio.h>
int main() {
    printf("That worked!\n");
    return 0;

Then create an executeable:

gcc -o firstpkg main.c

The default naming scheme for .deb packages looks like this:


We will replace these variables with the following:

Varibale Value
package-name firstpkg
version 0.0.1
release-number 1
architecture amd64/arm64

The release number is usually set to 1, only in case there was an error when packaging, this number would be changed.

Next create a directory with replaced variables and the subdirectories usr/bin and DEBIAN:

mkdir -p ~/build/firstpkg_0.0.1-1_amd64
mkdir -p ~/build/firstpkg_0.0.1-1_amd64/usr/bin/
mkdir -p ~/build/firstpkg_0.0.1-1_amd64/DEBIAN

Now copy the executeable to the binary directory:

cp ~/build/firstpkg/firstpkg ~/build/firstpkg_0.0.1-1_amd64/usr/bin/

To clearly identify packages, each package requires a control file under DEBIAN/:

touch ~/build/firstpkg_0.0.1-1_amd64/DEBIAN/control

Add the following lines to this file:

Package: firstpkg
Version: 0.0.1
Maintainer: example <>
Depends: libc6
Architecture: amd64
Description: just a test package
Remember to select the correct architecture (arm64/amd64/armhf/ or whatever you are creating the package for.)

Now build the package:

dpkg --build ~/build/firstpkg_0.0.1-1_amd64

If everything worked correctly there should be one file named firstpkg_0.0.1-1_amd64.deb as output in the current directory.

Finally you can view package information with:

dpkg-deb --info ~/build/firstpkg_0.0.1-1_amd64.deb
dpkg-deb --contents ~/build/firstpkg_0.0.1-1_amd64.deb

The output of the last command should contain the executeable.

Try to install this package with:

sudo apt install ./firstpkg_0.0.1-1_amd64.deb

And run it with:


If the output equals That worked! everything worked :).

Remove it again with:

sudo apt remove firstpkg

This part was sourced from - Creating and hosting your own deb packages.

Therefore this guide uses the apache webserver.

The root path should be /srv/repo/. Within this directory two subdirectories are required: pool/main and dists/stable. The the first one will contain all binaries, the second one the Release files.

mkdir -p /srv/repo/
mkdir -p /srv/repo/pool/main
mkdir -p /srv/repo/dists/stable

This guide uses the apache2 server, therefore install apache2:

apt install apache2

Next remove the default sites:

rm /etc/apache2/sites-available/000-default.conf
rm /etc/apache2/sites-available/default-ssl.conf

And create the new repository site::

sudo nano /etc/apache2/sites-available/repository.conf

The <servername> and <serveralias> variables are replaced with your servername, e.g. And the DocumentRoot should be set to the point of your filesystem, where your repository should start, e.g. /srv/repo.

Paste this content with changed variables to the site config:

Show/Hide repository.conf

To securely update from the repository the Releases file will be signed with a GPG - GNU Privacy Guard-key.

These keys will be generated in a safe environment, therefore create a temporary directory:

mktemp -d XXXX

The new four XXXX will automatically be replaced with random letters.

Next set the directory for GPG:

export GNUPGHOME=/path/to/tmp/dir

Finally generate the keypair with the following settings:

gpg --full-generate-key
Request Selection
Kind of Key 1 (RSA and RSA)
Keysize 3072
Expiring 0 (never)

A name must be entered, email address and comment can be left empty. You can also enter a password or just skip the prompts, it's your decision.

To view all created keys use this command:

gpg --list-secret-keys --keyid-format=long

Now create the public key file:

gpg --armor --export <ID> > public.key

Replace <ID> with the value behind sec rsaXXXX/ when running the command above. To make the public key accessible so that it can be installed, copy it to the root-path of the repository, e.g.:

cp public.key public.key /srv/repo/PublicReleaseKey.gpg

Also export the secret/private key file:

gpg --armor --export-secret-keys <ID> > private.key

Remind to store this file carefully!

The ID of the just created key is later required to sign the Release files properly, so maybe save this ID somewhere.

If getting the error No such file and directory when running gpg –full-generate-key try restarting the gpg agent:

gpgconf --kill gpg-agent

Sourced from

To create the Packages, Contents and Release files we use the command apt-ftparchive that is part of the apt-utils package.

apt-ftparchive requires two configuration files:

Show/Hide files

Now create the Packages and Contents file:

apt-ftparchive -c=/path/to/aptrelease.conf generate /path/to/aptgenerate.conf

Next create the unsigned Release file:

apt-ftparchive release -c=/path/to/aptrelease.conf <path-to-basedir> > <path-to-basedir>/Release

In this case path-to-basedir would be /srv/repo/dists/stable.

More information about the Release files can be found here:

Firstly create the Release.gpg file:

gpg --yes --pinentry-mode loopback --default-key <ID> -abs -o <path-to-basedir>/Release.gpg <path-to-basedir>/Release

path-to-basedir would again be /srv/repo/dists/stable, and ID the ID of the created GPG key: Create GPG Keys.

And finally create the InRelease file:

gpg --yes --pinentry-mode loopback --default-key <ID> --clearsign -o <path-to-basedir>/InRelease <path-to-basedir>/Release

This file will later be sourced from APT to index the repository.

Finally add your local repository. There are several ways:

Proper way

Firstly install the key:

curl -sS | gpg --dearmor | sudo tee /usr/share/keyrings/ > /dev/null

The command gets the file, dearmors the downloaded file and saves the content (key) to a file within the keysrings directory. The > /dev/null suppresses the unreadable output of the –dearmor command.

Then add the repository to /etc/apt/sources.list.d/:

echo "deb [signed-by=/usr/share/keyrings/] stable main" | sudo tee /etc/apt/sources.list.d/

Now apt can source your local repsitory securely, try it by installing the test-package:

sudo apt update && sudo apt install firstpkg

Deprecated ways

The most simple way is to 'install' the key:

curl -sS | sudo apt-key add -

But this will show warnings (! not errors), everytime you update., because it's a legacy method to store keys for apt-repositories.

There's another deprecated way:

curl -sS | gpg --dearmor | sudo tee /etc/apt/trusted.gpg.d/repository.gpg

It works also but isn't expected from the APT developers, consider using the method described above in this guide.

For both methods the repository has to be added this way:

sudo nano /etc/apt/sources.list.d/

With content:

# local - repository
deb stable main

There is a little helper when self-hosting a repository, more information can be found here: Extensions for self hosted Repository

When using a standard dir as GNUPGHOME, set correct permissions on this directory:

find ~/.gnupg -type f -exec chmod 600 {} \;
find ~/.gnupg -type d -exec chmod 700 {} \;

Explanation for 600, 700:

Lets start from the back: 00 mean NO rights AT ALL for everybody who is not the owner of the files/directories.

That means, that the process reading these (gnupg) must run as the owner of these files/directories.

~/.gnupg/ is a folder, the process reading the contents must be able to “enter” (=execute) this folder. This is the “x” Bit. It has the value 1. 7 - 6 = 1

Both ~/.gnupg/ and ~/.gnupg/* you want to be able to read and write, thats 4 + 2 = 6.

⇒ Only the owner of the files can read/write them now (=600). Only he can enter into the directory as well (=700)

⇒ These file rights don't “need” to be documented, they are derivable from the intended usage.

More info about permission notation: - FS permissions

Sourced from - Correct permissions for .gnupg file dir

Sign Release file automatically with passphrase:

gpg --yes --pinentry-mode loopback --passphrase-file /path/to/passphrase-file --default-key <ID> --clearsign -o /path/to/InRelease /path/to/Release


Create Packages file with dpkg-scanpackages: (run in base directory of repository e.g. /srv/repo/)

dpkg-scanpackages --arch amd64 pool/ > dists/stable/main/binary-amd64/Packages

Create compressed Packages file:

cat dists/stable/main/binary-amd64/Packages | gzip -9 > dists/stable/main/binary-amd64/Packages.gz

Generate Package files:

apt-ftparchive generate /path/to/aptgenerate.conf

Create Release file:

apt-ftparchive -c /root/apt-ftp-files/aptrelease.conf release /srv/ext-stor/repo/ > /srv/ext-stor/repo/dists/stable/Release

Signing Release file:

gpg --default-key <ID> -abs -o Release.gpg Release

Signing InRelease file:

gpg --default-key <ID> --clearsign -o InRelease Release

Apache config to hide .db files from apt-ftparchive, add this to site repository.conf:

<Files packages-amd64.db>
	Require all denied

Automated file signing of password protected keys: - Sign files with gpg automatically with password-protected keys

  • linux/debian/setup-repository.txt
  • Last modified: 2023/07/30 12:16
  • by Zyzonix