$linuxjunkies
>

Build a .deb Package

Step-by-step guide to building a .deb package using dh_make, debian/control, debian/rules, lintian quality checks, and clean chroot builds with sbuild.

IntermediateUbuntuDebianFedoraArch10 min readUpdated June 7, 2026

Before you start

  • A Debian or Ubuntu system with sudo access
  • Source code or a tarball of the software you want to package
  • Basic familiarity with Makefiles and compiling C or similar projects
  • Internet access for sbuild chroot creation

Building a .deb package lets you distribute software cleanly across Debian-based systems, manage dependencies formally, and integrate with apt. The toolchain—dh_make, the debian/ directory, and sbuild—has a reputation for complexity, but each piece has a clear job. This guide walks from source tarball to a lintian-clean package built inside a clean chroot.

Prerequisites and Tooling

Work on Ubuntu 24.04 LTS or Debian 12. Install the packaging toolchain:

sudo apt install build-essential devscripts dh-make lintian sbuild schroot debootstrap

Add yourself to the sbuild group so you can use the build chroot without sudo:

sudo adduser $USER sbuild
newgrp sbuild

Step 1: Prepare the Source Tarball

Debian packaging expects a specific naming convention: package_version.orig.tar.gz. If you are packaging something you wrote, produce the tarball yourself. If it is third-party, download the upstream release.

mkdir ~/packaging && cd ~/packaging
# Example: a small C program called 'hellocli' version 1.0
mkdir hellocli-1.0
tar czf hellocli_1.0.orig.tar.gz hellocli-1.0/

The underscore before the version and .orig before the extension are mandatory—tools like dpkg-source key on them.

Step 2: Run dh_make to Scaffold the debian/ Directory

Enter the unpacked source directory and run dh_make. The -p flag sets the package name and version explicitly; -s requests a single binary package.

cd hellocli-1.0
dh_make -p hellocli_1.0 -s -f ../hellocli_1.0.orig.tar.gz

dh_make creates a debian/ subdirectory with template files. Most of the .ex and .EX example files can be deleted if you do not need them:

rm debian/*.ex debian/*.EX

Step 3: Edit debian/control

debian/control is the central metadata file. It has two stanzas: one for the source package and one for each binary package produced. Open it in your editor and fill in the fields accurately.

Source: hellocli
Section: utils
Priority: optional
Maintainer: Ada Lovelace <[email protected]>
Build-Depends: debhelper-compat (= 13)
Standards-Version: 4.6.2
Homepage: https://example.com/hellocli

Package: hellocli
Architecture: amd64
Depends: ${shlibs:Depends}, ${misc:Depends}
Description: A minimal CLI greeting tool
 A one-line greeting utility demonstrating Debian packaging.
 This is the long description; indent continuation lines with a space.
  • Build-Depends: list everything needed to compile. debhelper-compat (= 13) pins the debhelper compatibility level and replaces the old debian/compat file.
  • Depends: ${shlibs:Depends} is a substitution variable filled automatically by dpkg-shlibdeps; leave it unless you are packaging a pure Python or shell package.
  • Standards-Version: use the current value from the Debian Policy Manual. Lintian will warn if it is stale.

Step 4: Edit debian/rules

debian/rules is an executable Makefile. With debhelper 13, the minimal version delegates everything to the dh command sequence:

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

That single rule handles configure, build, install, and clean via debhelper helpers (dh_auto_configure, dh_auto_build, dh_auto_install, etc.). If your project uses a non-standard build system, override specific targets:

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

override_dh_auto_build:
	$(MAKE) PREFIX=/usr

Make sure the file is executable:

chmod +x debian/rules

Step 5: Fill in Remaining debian/ Files

debian/changelog

dh_make creates this with a placeholder. The format must be exact—use dch to edit it safely:

dch --create -v 1.0-1 --package hellocli "Initial release"

The trailing -1 is the Debian revision. The distribution should match your target: unstable, noble, bookworm, etc.

Fill in the license and upstream copyright. Lintian enforces the DEP-5 machine-readable format. A minimal example:

Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Upstream-Name: hellocli
Source: https://example.com/hellocli

Files: *
Copyright: 2024 Ada Lovelace <[email protected]>
License: MIT

License: MIT
 Permission is hereby granted, free of charge ...

Step 6: Build Locally with dpkg-buildpackage (Quick Test)

Before committing to an sbuild chroot, do a fast local build to catch obvious errors:

cd ~/packaging/hellocli-1.0
dpkg-buildpackage -us -uc -b

-us -uc skips GPG signing. -b builds binary packages only. Artifacts land one level up in ~/packaging/. Fix any compiler or install errors before moving on.

Step 7: Run Lintian

Lintian is the Debian package linter. Run it against the freshly built .deb:

lintian --pedantic --display-info ../hellocli_1.0-1_amd64.deb

Common issues and fixes:

  • W: no-manual-page: add a man page or suppress with override_dh_missing if appropriate.
  • E: bad-distribution-in-changes-file: the changelog distribution name does not match a known suite—fix debian/changelog with dch -r.
  • W: standards-version-is-deprecated: update the Standards-Version field in debian/control.

Aim for zero errors and zero warnings before moving to the chroot build. Pedantic tags are advisory but worth reviewing.

Step 8: Build in a Clean Chroot with sbuild

A local build uses your host's installed libraries, which can mask missing Build-Depends. sbuild builds inside a minimal chroot, matching what a Debian build server does.

Create the chroot

sudo sbuild-createchroot --include=eatmydata,ccache,gnupg \
  noble /srv/chroot/noble-amd64 \
  http://archive.ubuntu.com/ubuntu/

Replace noble with your target suite (bookworm for Debian 12). This takes a few minutes and requires internet access.

Run the chroot build

cd ~/packaging/hellocli-1.0
sbuild -d noble -j$(nproc) ../hellocli_1.0-1.dsc

Pass ../hellocli_1.0-1.dsc—the source control file produced by dpkg-buildpackage. If the .dsc is missing, generate it first:

dpkg-buildpackage -us -uc -S

A successful sbuild run produces .deb, .buildinfo, and .changes files. Run lintian again on the chroot-built .deb for a definitive result.

Verification

Install and smoke-test the package on a matching system:

sudo dpkg -i ../hellocli_1.0-1_amd64.deb
hellocli --version
apt-cache show hellocli

Check that dependencies resolve correctly by installing through apt if you have a local repo, or with gdebi which resolves deps for local .deb files:

sudo apt install gdebi-core
sudo gdebi ../hellocli_1.0-1_amd64.deb

Troubleshooting

  • dpkg-source: error: ... about unexpected files: add them to debian/.gitignore or debian/source/options with extend-diff-ignore.
  • sbuild fails to resolve Build-Depends: the chroot's apt lists may be stale. Run sudo sbuild-update -udcar noble to refresh it.
  • dh_auto_install fails: your upstream Makefile's install target may not respect DESTDIR. Override dh_auto_install in debian/rules and pass DESTDIR=$(CURDIR)/debian/hellocli explicitly.
  • Lintian E: debian-changelog-has-wrong-version: the version in debian/changelog must exactly match the version used in the .orig.tar.gz filename.
tested on:Ubuntu 24.04Debian 12

Frequently asked questions

What is the difference between the source package version and the Debian revision?
The version string is split at the last hyphen: everything before it is the upstream version, everything after is the Debian revision. For '1.0-1', upstream is 1.0 and the Debian revision is 1. Increment the Debian revision when you change packaging without changing upstream source.
Why use sbuild instead of just dpkg-buildpackage?
dpkg-buildpackage uses your host's installed packages, so a missing Build-Depends will succeed locally but fail on a clean system. sbuild builds inside a minimal chroot that contains only essential packages plus your declared Build-Depends, catching dependency omissions before they reach users.
Can I package a Python or Go program the same way?
The debian/ directory structure is identical, but Build-Depends and the dh sequence change. For Python use dh-python and add python3-setuptools to Build-Depends. For Go use dh-golang. The ${shlibs:Depends} substitution variable is typically not needed for pure interpreted or statically linked packages.
How do I handle a package that installs a systemd service?
Place the unit file at debian/packagename.service. debhelper's dh_installsystemd helper picks it up automatically at compat level 13 and adds the correct postinst/prerm snippets to enable and start the service on install.
What does lintian E: vs W: vs I: mean?
E (error) indicates a policy violation or a definite bug that will likely cause installation or runtime failures. W (warning) is a probable issue worth fixing. I (info) and P (pedantic) are advisory. Packages uploaded to official Debian repos must have zero errors and ideally zero warnings.

Related guides