|

Packaging Your Own Software for a PPA (From Scratch)

← Previous Linux PPA Packaging (4/4) Next →

Example Values Used in This Tutorial

This tutorial uses the following fake example values throughout. Replace them with your own real details when following along.

FieldExample Value
Full NameRex Bytes
Email[email protected]
Launchpad Usernamerexbytes
GPG Key IDAABB1122CCDD3344
GPG Fingerprint1A2B3C4D5E6F7890AABB1122CCDD3344EEFF5566
PPAppa:rexbytes/example-ppa
Packagehello-ppa 1.0.0
PPA Version1.0.0-0ppa1~rexbytes
Target Distributionnoble
Working Directory~/packaging/hello-ppa

Prerequisites: You should be familiar with (GPG/SSH setup) and (PPA upload workflow) before continuing.


In tutorials 02 and 03, you packaged someone else’s upstream project (cmatrix). This tutorial covers the other common case: you wrote your own software and want to package it for a PPA from scratch — no upstream tarball to download, no existing Debian packaging to borrow from.

You’ll learn how to:

  • Structure your own project so it’s ready for Debian packaging
  • Create the orig.tar.gz from your own source code
  • Write all the debian/ files by hand
  • Build and test the .deb locally
  • Build a signed source package and upload to your PPA

We’ll use a small example project: hello-ppa, a simple Python command-line tool.


0. What’s different when there’s no upstream?

In tutorials 02 and 03, the workflow was:

  1. Download an upstream tarball (someone else’s code)
  2. Create debian/ packaging around it
  3. Use quilt to patch the upstream source

When you’re packaging your own code, the differences are:

Upstream packaging (tutorials 02–03)From-scratch packaging (this tutorial)
Download someone else’s tarballYou create the tarball from your own source
May need quilt patches to modify upstreamNo patches needed — just edit your code directly
Version like 2.0-1~ppa1~rexbytesVersion like 1.0.0-0ppa1~rexbytes (the -0 signals no prior Debian packaging)
debian/copyright credits upstream authorsdebian/copyright credits you
Build system comes from upstream (autotools, cmake, etc.)You choose your own build/install method

Key insight: You still create a <package>_<version>.orig.tar.gz tarball even for your own code. Debian tooling expects it. The only difference is that you are the upstream.


1. Install required tools

sudo apt update
sudo apt install devscripts build-essential debhelper dput gnupg python3


  • devscripts — provides debuild, dch, and other packaging tools.
  • debhelper — the modern Debian build helper framework (also provides the debhelper-compat virtual package used in Build-Depends).
  • dput — uploads source packages to your PPA.
  • gnupg — for GPG signing.
  • python3 — our example project is a Python script.

2. Debian package naming rules

Before creating your project, you need to choose a valid package name. Debian tooling enforces strict naming rules — an invalid name will cause build failures or rejections.

2.1 Valid package names

A Debian package name must:

  • Contain only lowercase letters (a-z), digits (0-9), plus (+), minus (-), and period (.)
  • Start with an alphanumeric character
  • Be at least 2 characters long
NameValid?Why
hello-ppaYesLowercase, starts with letter, uses - separator
cmatrixYesLowercase alphanumeric
libfoo-devYesCommon convention for development headers
MyAppNoUppercase letters not allowed
my_appNoUnderscores not allowed in package names
-badnameNoMust start with an alphanumeric character

Tip: If your project uses underscores or capitals (e.g. My_Cool_App), convert to lowercase with hyphens for the package name: my-cool-app.

2.2 File naming conventions

Debian tooling uses a consistent pattern across all generated files. The key distinction: hyphens separate name from version in directories, underscores separate them in files.

File typePatternExample
Source directory<package>-<version>/hello-ppa-1.0.0/
Orig tarball<package>_<version>.orig.tar.gzhello-ppa_1.0.0.orig.tar.gz
Debian tarball<package>_<fullversion>.debian.tar.xzhello-ppa_1.0.0-0ppa1~rexbytes.debian.tar.xz
Source description<package>_<fullversion>.dschello-ppa_1.0.0-0ppa1~rexbytes.dsc
Binary package<package>_<fullversion>_<arch>.debhello-ppa_1.0.0-0ppa1~rexbytes_all.deb
Changes file<package>_<fullversion>_<arch>.changeshello-ppa_1.0.0-0ppa1~rexbytes_amd64.changes
Build info<package>_<fullversion>_<arch>.buildinfohello-ppa_1.0.0-0ppa1~rexbytes_amd64.buildinfo

Important: Getting the separator wrong (e.g. using a hyphen instead of an underscore in the tarball name) will cause dpkg-source to fail because it can’t find the matching tarball.

2.3 The ~ tilde in version strings

The tilde (~) is a special character in Debian version sorting. It sorts lower than anything — even lower than the empty string. This makes it the standard tool for creating versions that will be automatically superseded by official packages.

Sort order (lowest to highest):

1.0~alpha1lowest (~ sorts before everything)
1.0~beta1
1.0~rc1
1.0release (no suffix)
1.0-1~ppa1~rexlabelled PPA build (even lower than ~ppa1)
1.0-1~ppa1PPA build (lower than 1.0-1)
1.0-1~ppa2second PPA build (still lower than 1.0-1)
1.0-1first Debian revision
1.0-2second Debian revision
1.0+dfsg1          ← + sorts higher (above all 1.0-* revisions)


Code language: CSS (css)

This is why PPA versions use ~ppa1 — if Ubuntu later publishes an official package at 1.0-1, apt will automatically upgrade users from 1.0-1~ppa1 to 1.0-1. Each additional ~ suffix pushes the version lower still, which is why ~ppa1~rexbytes sorts below ~ppa1.

Rule of thumb: Use ~ when you want your version to be automatically replaced by a “real” release. Use + when you want your version to sort higher (e.g. +dfsg1 for DFSG-cleaned tarballs).

2.4 Source vs binary package names

In debian/control, the source package and binary package are declared separately:

Source: hello-ppasource package name
...

Package: hello-ppabinary package name (the .deb)


Code language: CSS (css)

For simple packages like ours, they’re the same. But a single source package can produce multiple binary packages. For example, a C library source package libfoo might produce:

  • libfoo1 — the shared library (the 1 is the ABI version)
  • libfoo-dev — development headers and static library
  • libfoo-doc — documentation

For PPA packaging of your own software, keeping source and binary names identical is the normal approach.


3. Create your project

Start by writing the software you want to package. For this tutorial, we’ll create a small Python CLI tool called hello-ppa.

3.1 Set up the project directory

mkdir -p ~/packaging/hello-ppa/hello-ppa-1.0.0
cd ~/packaging/hello-ppa/hello-ppa-1.0.0


Code language: JavaScript (javascript)

The directory name must follow the pattern <package>-<version> (e.g. hello-ppa-1.0.0). Debian tooling relies on this convention.

3.2 Write the program

Create the main script:

nano hello-ppa


Insert:

#!/usr/bin/env python3
"""hello-ppa: a simple greeting tool, packaged for a PPA."""

import argparse


def main():
    parser = argparse.ArgumentParser(
        description="A simple greeting tool from your PPA."
    )
    parser.add_argument(
        "name",
        nargs="?",
        default="World",
        help="Name to greet (default: World)",
    )
    parser.add_argument(
        "-v", "--version",
        action="version",
        version="hello-ppa 1.0.0",
    )
    args = parser.parse_args()
    print(f"Hello, {args.name}! Greetings from your PPA.")


if __name__ == "__main__":
    main()


Code language: JavaScript (javascript)

Make it executable:

chmod +x hello-ppa


Test it:

./hello-ppa
./hello-ppa Rex
./hello-ppa --version


Expected output:

Hello, World! Greetings from your PPA.
Hello, Rex! Greetings from your PPA.
hello-ppa 1.0.0


Code language: JavaScript (javascript)

3.3 Create a simple Makefile

Debian packaging uses make install (via debhelper) to put files in the right place. Create a Makefile that installs the script into the system:

nano Makefile


Insert:

PREFIX ?= /usr

install:
	install -d $(DESTDIR)$(PREFIX)/bin
	install -m 755 hello-ppa $(DESTDIR)$(PREFIX)/bin/hello-ppa


Note: The DESTDIR variable is set by debhelper during the build. It points to a temporary staging directory — your script won’t be installed to the real /usr/bin during packaging. install -d creates the target directory, and install -m 755 copies the file with executable permissions.

Test the Makefile locally (optional):

make install DESTDIR=/tmp/hello-ppa-test
ls -l /tmp/hello-ppa-test/usr/bin/hello-ppa


Code language: JavaScript (javascript)

Clean up:

rm -rf /tmp/hello-ppa-test



4. Create the orig tarball

Even though you wrote this code yourself, Debian tooling expects an orig.tar.gz sitting alongside the source directory.

Go up to the packaging directory and create it:

cd ~/packaging/hello-ppa
tar -czf hello-ppa_1.0.0.orig.tar.gz hello-ppa-1.0.0


Code language: JavaScript (javascript)

Your directory should now look like:

~/packaging/hello-ppa/
├── hello-ppa_1.0.0.orig.tar.gz
└── hello-ppa-1.0.0/
    ├── hello-ppa
    └── Makefile


Code language: JavaScript (javascript)

Important: The tarball must be named <package>_<version>.orig.tar.gz — note the underscore between package name and version. The directory inside should be <package>-<version> with a hyphen.


5. Create the debian/ directory

Go into the source directory and create the packaging files:

cd ~/packaging/hello-ppa/hello-ppa-1.0.0
mkdir debian


Code language: JavaScript (javascript)

We’ll create these files:

  • debian/control
  • debian/rules
  • debian/changelog
  • debian/copyright
  • debian/source/format

6. Create debian/control

nano debian/control


Insert:

Source: hello-ppa
Section: utils
Priority: optional
Maintainer: Rex Bytes <rexbytes@example.com>
Build-Depends: debhelper-compat (= 13)
Standards-Version: 4.7.0
Homepage: https://launchpad.net/~rexbytes/+archive/ubuntu/example-ppa

Package: hello-ppa
Architecture: all
Depends: python3, ${misc:Depends}
Description: Simple greeting tool from a PPA
 A minimal command-line tool that prints a friendly greeting.
 Created as part of a PPA packaging tutorial to demonstrate
 packaging your own software from scratch.


Code language: CSS (css)

Key differences from the cmatrix tutorials:

  • Architecture: all — our script is pure Python, so it runs on any architecture. For compiled C/C++ code you’d use Architecture: any.
  • Depends: python3 — we explicitly list python3 as a runtime dependency since our script needs it. There’s no ${shlibs:Depends} because we don’t have compiled shared libraries.
  • Build-Depends is minimal — no autoconf, no library headers. Just debhelper-compat. The debhelper-compat (= 13) entry is a virtual package that tells debhelper which compatibility level to use — level 13 is the current recommended default. This replaces the older debian/compat file.

7. Create debian/rules

nano debian/rules


Insert:

#!/usr/bin/make -f
%:
	dh $@


Code language: JavaScript (javascript)

That’s the entire file. Because our Makefile follows standard conventions (make install with DESTDIR and PREFIX), debhelper can handle everything automatically. No overrides needed.

Make it executable:

chmod +x debian/rules


Important: debian/rules is a Makefile. Indented lines must use a real tab character, not spaces. If you copy-paste from a web page, your editor may convert tabs to spaces — this will cause build failures with missing separator. In nano, press Tab to insert a literal tab.

Why is this so much simpler than the cmatrix tutorials? The cmatrix package needed override_dh_auto_configure to run autoreconf and ./configure. Our project has a straightforward Makefile with an install target, which is exactly what debhelper expects by default.


8. Create debian/changelog

Set your identity for dch:

export DEBFULLNAME="Rex Bytes"
export DEBEMAIL="[email protected]"


Code language: JavaScript (javascript)

Create the initial changelog:

dch --create -v 1.0.0-0ppa1~rexbytes --package hello-ppa -D noble


Code language: CSS (css)

Important: Without -D noble, dch defaults the distribution to UNRELEASED. Launchpad rejects uploads with UNRELEASED — you must set it to an active Ubuntu series like noble.

dch will open an editor. Verify the distribution says noble and write a description:

hello-ppa (1.0.0-0ppa1~rexbytes) noble; urgency=medium

  * Initial release: simple greeting tool for PPA packaging tutorial.

 -- Rex Bytes <[email protected]>  Mon, 09 Feb 2026 12:00:00 +0000


Code language: HTML, XML (xml)

Save and exit.

Understanding the version: 1.0.0-0ppa1~rexbytes

PartMeaning
1.0.0Your software’s version. You control this.
-0Debian revision 0 — signals that this package has no prior official Debian packaging. A -0 revision is the convention for packages that exist only in a PPA and have never been in the official Ubuntu/Debian archive. If Debian or Ubuntu ever packages your software officially as 1.0.0-1, apt will upgrade users to the official version automatically.
ppa1First PPA build of this version. Bump to ppa2, ppa3, etc. for packaging-only fixes.
~rexbytesLabels this as coming from your PPA. The ~ prefix keeps the version lower than a bare -0ppa1.

Why -0 instead of -1? In the cmatrix tutorials we used -1 because cmatrix already exists in the Debian/Ubuntu archive. For software that has never been in the official archive, -0 is the correct convention — it signals “no official Debian packaging exists yet.”


9. Create debian/copyright

nano debian/copyright


Insert:

Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Upstream-Name: hello-ppa
Source: https://launchpad.net/~rexbytes/+archive/ubuntu/example-ppa

Files: *
Copyright: 2026 Rex Bytes <[email protected]>
License: MIT

Files: debian/*
Copyright: 2026 Rex Bytes <rexbytes@example.com>
License: MIT

License: MIT
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
 in the Software without restriction, including without limitation the rights
 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 copies of the Software, and to permit persons to whom the Software is
 furnished to do so, subject to the following conditions:
 .
 The above copyright notice and this permission notice shall be included in all
 copies or substantial portions of the Software.
 .
 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 SOFTWARE.


Code language: PHP (php)

Note: Since you’re the author, you choose the license. This example uses MIT. Use GPL, Apache, BSD, or whatever suits your project. The key thing is that debian/copyright must accurately reflect the license of your code.


10. Create debian/source/format

This file tells dpkg-source which source package format to use. Without it, dpkg-source defaults to the legacy format 1.0 and emits a warning.

mkdir -p debian/source
echo '3.0 (quilt)' > debian/source/format


Code language: PHP (php)

Format 3.0 (quilt) is the correct choice here because we have a separate orig.tar.gz tarball. Even though you’re the upstream author and don’t need quilt patches, the 3.0 (quilt) format is required whenever you have an orig tarball with a Debian revision in the version (e.g. 1.0.0-0ppa1~rexbytes).


11. Build the package locally

Go to the source directory and build:

cd ~/packaging/hello-ppa/hello-ppa-1.0.0
dpkg-buildpackage -us -uc


Code language: JavaScript (javascript)
  • -us = do not sign the source package.
  • -uc = do not sign the .changes file.

Note: dpkg-buildpackage does not run lintian automatically (that is a feature of debuild). After the build finishes, run lintian yourself to check for packaging issues:

lintian ../hello-ppa_1.0.0-0ppa1~rexbytes_all.changes


You may see warnings about missing man pages or other minor policy issues — these don’t block the build and are normal for tutorial packages. To see detailed explanations of any warnings, add the --info flag.

If the build succeeds, go up one directory to see the output:

cd ..
ls


You should see:

hello-ppa_1.0.0-0ppa1~rexbytes_all.deb
hello-ppa_1.0.0-0ppa1~rexbytes.dsc
hello-ppa_1.0.0-0ppa1~rexbytes.debian.tar.xz
hello-ppa_1.0.0-0ppa1~rexbytes_amd64.buildinfo
hello-ppa_1.0.0-0ppa1~rexbytes_amd64.changes
hello-ppa_1.0.0.orig.tar.gz
hello-ppa-1.0.0/


Note: The .deb says _all.deb (not _amd64.deb) because we set Architecture: all — this package works on any architecture.

Troubleshooting common build failures

SymptomLikely causeFix
missing separator in debian/rulesSpaces instead of tabsRe-indent with real tab characters (use Tab key in your editor)
No such file or directory: hello-ppa_1.0.0.orig.tar.gzMissing or misnamed orig tarballEnsure hello-ppa_1.0.0.orig.tar.gz is in ~/packaging/hello-ppa/ (one level above the source directory)
install: cannot stat 'hello-ppa': No such file or directoryRunning the build from the wrong directoryMake sure you’re in ~/packaging/hello-ppa/hello-ppa-1.0.0 and the hello-ppa script exists
dh: error: unable to determine compat levelMissing debhelper-compat in Build-DependsCheck that debian/control has Build-Depends: debhelper-compat (= 13)
distribution UNRELEASED is not allowed (later, at upload time)Changelog still says UNRELEASEDRe-run dch -D noble or edit debian/changelog to set the distribution to noble

12. Install and test locally

sudo dpkg -i hello-ppa_1.0.0-0ppa1~rexbytes_all.deb


Code language: CSS (css)

If dpkg complains about missing dependencies, fix them with:

sudo apt -f install


Now test:

hello-ppa
hello-ppa Rex
hello-ppa --version


Expected output:

Hello, World! Greetings from your PPA.
Hello, Rex! Greetings from your PPA.
hello-ppa 1.0.0


Code language: JavaScript (javascript)

Confirm the installed version:

dpkg -l hello-ppa


To uninstall after testing:

sudo apt remove hello-ppa



13. Build a signed source package for your PPA

Once the local build works, create a signed source package for Launchpad.

Make sure your packaging identity matches your GPG key:

export DEBFULLNAME="Rex Bytes"
export DEBEMAIL="[email protected]"


Code language: JavaScript (javascript)

Verify your GPG key:

gpg --list-secret-keys --keyid-format LONG


Code language: PHP (php)

From inside ~/packaging/hello-ppa/hello-ppa-1.0.0:

13.1 First upload of this version

Include the orig tarball and sign:

debuild -S -sa -k<YOUR-FINGERPRINT>


Code language: HTML, XML (xml)

For example, using the tutorial’s fake fingerprint:

debuild -S -sa -k1A2B3C4D5E6F7890AABB1122CCDD3344EEFF5566


  • -S → source-only build (no binary .deb).
  • -sa → include the orig.tar.gz in the upload.
  • -k… → GPG key fingerprint to sign with. Replace with your own.

13.2 Subsequent uploads (same upstream version)

If you only changed the debian/ files (e.g. bumped to 1.0.0-0ppa2~rexbytes):

debuild -S -sd -k<YOUR-FINGERPRINT>


Code language: HTML, XML (xml)
  • -sd → skip re-uploading the orig tarball (Launchpad already has it).

14. Upload to your PPA

cd ~/packaging/hello-ppa
dput ppa:rexbytes/example-ppa hello-ppa_1.0.0-0ppa1~rexbytes_source.changes


Code language: JavaScript (javascript)

After uploading:

  1. Check your email — Launchpad sends an Accepted or Rejected notification.
  2. Visit your PPA page on Launchpad to watch the build progress.
  3. Once the status shows Published, the package is available for installation.

Common rejection reasons:

  • GPG signature doesn’t match a key registered on your Launchpad account
  • Email in debian/changelog doesn’t match a verified Launchpad email
  • Version already exists in the PPA (you need to bump the version number)
  • The distribution (e.g. noble) in the changelog doesn’t match an active Ubuntu series

15. Install from your PPA

Once published, anyone can install your package:

sudo add-apt-repository ppa:rexbytes/example-ppa
sudo apt update
sudo apt install hello-ppa


Verify it came from your PPA:

apt-cache policy hello-ppa


Expected output:

hello-ppa:
  Installed: 1.0.0-0ppa1~rexbytes
  Candidate: 1.0.0-0ppa1~rexbytes
  Version table:
 *** 1.0.0-0ppa1~rexbytes 500
        500 https://ppa.launchpadcontent.net/rexbytes/example-ppa/ubuntu noble/main amd64 Packages
        100 /var/lib/dpkg/status


Code language: JavaScript (javascript)

16. Releasing a new version

When you update your software, here’s the workflow:

16.1 Update your code

Make changes to your source files (e.g. edit hello-ppa to add features).

16.2 Create a new orig tarball

If the upstream version changed (e.g. 1.0.01.1.0):

cd ~/packaging/hello-ppa

# Rename the source directory to match the new version
mv hello-ppa-1.0.0 hello-ppa-1.1.0

# Create a new orig tarball
tar -czf hello-ppa_1.1.0.orig.tar.gz hello-ppa-1.1.0


Code language: PHP (php)

16.3 Update the changelog

cd hello-ppa-1.1.0
dch -v 1.1.0-0ppa1~rexbytes -D noble "New upstream release: added feature X."


Code language: CSS (css)

Verify the distribution says noble, save and exit.

16.4 Build and upload

debuild -S -sa -k<YOUR-FINGERPRINT>
cd ..
dput ppa:rexbytes/example-ppa hello-ppa_1.1.0-0ppa1~rexbytes_source.changes


Code language: HTML, XML (xml)

16.5 Packaging-only fixes (no code changes)

If you only need to fix the debian/ files without changing your software:

dch -v 1.0.0-0ppa2~rexbytes -D noble "Fix packaging: corrected dependency."
debuild -S -sd -k<YOUR-FINGERPRINT>
cd ..
dput ppa:rexbytes/example-ppa hello-ppa_1.0.0-0ppa2~rexbytes_source.changes


Code language: HTML, XML (xml)

Note -sd instead of -sa — the orig tarball hasn’t changed, so no need to re-upload it.


17. Summary: from-scratch vs upstream packaging

StepUpstream (tutorials 02–03)From scratch (this tutorial)
Get sourceDownload tarball / apt sourceWrite it yourself
Orig tarballRename downloaded tarballtar -czf your source directory
PatchesUse quilt to modify upstream codeEdit your code directly — no quilt needed
debian/rulesOften needs overrides (autoreconf, configure)Minimal — dh $@ often suffices
debian/controlArchitecture: any for compiled codeArchitecture: all for scripts, any for compiled
Version-1~ppa1~rexbytes (prior Debian packaging exists)-0ppa1~rexbytes (no prior Debian packaging)
CopyrightCredit upstream authorsCredit yourself

The core Debian packaging concepts — debian/control, debian/rules, debian/changelog, debian/copyright, orig tarballs, debuild, dput — are the same regardless of whether you’re packaging your own code or someone else’s.

Similar Posts

Leave a Reply