Build Debian Packages Reliably with debian/rules
You will finish with an executable debian/rules Makefile that exposes the targets Debian package tools expect, keeps ordinary build work unprivileged, and makes the privileged boundary visible. This is a small file, but a missing target or an incorrect dependency can make a source package fail late in the build.
The route
Jump straight to the step you need, or tick off Done means at the end.
Allow about 20 minutes for a first rules file and a further 10 minutes to test it. You need the make command, the dpkg-dev package, a Debian source-package directory, and permission to edit that directory. The examples below use a placeholder package called sample-tool; replace it with the package you are actually building.
Checkpoint 1: confirm the local contract
This guide follows the deb-src-rules(5) shipped by dpkg-dev 1.22.6ubuntu6.6 on the system used for this guide. The current installed manual says that debian/rules is an executable Makefile, normally started with #!/usr/bin/make -f. Check your own installation before relying on version-specific details:
dpkg-query -W -f='${Package} ${Version}\n' dpkg-dev
man deb-src-rules
The required target names are clean, build-indep, build-arch, build, binary-indep, binary-arch, and binary. The manual describes the interface and privilege expectations; it does not prescribe the commands inside each target.
Step 1: create the executable file
From the top of the source tree, create debian/rules with a shebang and a minimal dependency graph. This example is deliberately conservative: the build targets create a harmless marker, while the binary targets show the required relationships without pretending to build a real package.
cd /path/to/sample-tool
mkdir -p debian
touch debian/rules
chmod 0755 debian/rules
#!/usr/bin/make -f
build-indep build-arch: build-stamp
build: build-indep build-arch
build-stamp:
\tprintf '%s\n' 'build completed' > $@
binary-indep: build-indep
binary-arch: build-arch
binary: binary-indep binary-arch
clean:
\trm -f build-stamp
The tab before each command is significant. A run that reports "missing separator" usually has spaces where Make requires a tab. A real package will replace the marker recipe with its compiler, generator, and packaging commands. If it uses debhelper, its rules file may delegate unknown targets to debhelper, but that requires the package's declared build dependencies and should not be copied into an unrelated project.
Step 2: keep build and binary responsibilities separate
build-indep is for files needed by architecture-independent packages. build-arch is for files needed by architecture-dependent packages. Both must exist even when that package type does not exist, so an empty target is valid for a package that has nothing to do in that branch.
build must cover the independent and dependent build work, either by depending on those targets or by doing equivalent work itself. The example uses dependencies so that a caller selecting one branch does not accidentally run unrelated work.
The binary targets produce packages. binary-indep must reach build-indep or build; binary-arch must reach build-arch or build; and binary must reach the binary branches. These are Make dependencies, not comments: they are what lets tools request a specific package class safely.
Do not make a build target require root privileges. The manual says that clean, binary-indep, binary-arch, and binary are called with root privileges, but a clean implementation should still work when invoked by an ordinary user wherever possible. Root can hide ownership mistakes and make the next build harder to reproduce.
Step 3: verify the graph as an ordinary user
Run the non-privileged targets first. These commands change files inside your source tree, so inspect the directory before running them if it contains uncommitted work.
make -f debian/rules build-indep
make -f debian/rules build-arch
make -f debian/rules build
test -f build-stamp && printf '%s\n' 'build-stamp exists'
Expected output ends with:
build-stamp exists
Make may say that a target is already up to date on later runs. That is normal. Verify the dependency structure directly with a dry run:
make -n -f debian/rules binary
The dry run should show the commands needed by both build branches before the binary work. Because -n does not execute commands, it is a useful checkpoint when a real binary target would invoke compilers, generators, or installers.
Step 4: test cleanup deliberately
Warning
clean is allowed to remove build products and is called with root privileges by package tooling. Read the recipe before running it, and do not use a broad removal command that could reach outside the source tree.
make -f debian/rules clean
test ! -e build-stamp && printf '%s\n' 'clean removed the marker'
Expected output is:
clean removed the marker
If cleanup fails part-way through, restore any tracked files from version control using your normal project workflow, then remove only the generated paths you have identified. Do not run the binary targets with sudo merely to get past a permissions error; find which recipe created the wrong ownership and fix that recipe.
Step 5: connect the file to a real package build
Once the rules file contains real package commands, use the package builder from the source-tree parent:
cd /path/to
dpkg-buildpackage -us -uc
The -us and -uc options avoid signing the source and changes files during a local test. They do not make a build safe if the rules file itself performs unsafe actions. A successful build writes package artefacts beside the source directory, not inside debian/. Check the result without installing it:
ls -l ../sample-tool_*.deb ../sample-tool_*.changes 2>/dev/null
If you only need to test the file's interface, return to make -f debian/rules build and the dry-run check. Package creation can invoke maintainer scripts or other tooling, so treat an actual install as a separate, deliberate test. Nothing in deb-src-rules(5) says that a package should be installed as part of its build.
Common traps
- A target that is absent because the package has no architecture-independent or architecture-dependent output still violates the interface. Keep the target and make it do nothing.
- A
binary-archtarget that does not depend onbuild-archcan produce a package from stale or missing files. Express the dependency in Make. - Commands that need root during
buildmake reproducible builds difficult and can leave root-owned files in a user's checkout. - Using
sudo make cleancan conceal the ownership problem and make later cleanup require more privileges. - Changing the shebang, target names, or tabs while copying an example can turn a valid Makefile into a file that fails before any package command runs.
Done means
debian/rulesis executable and begins with a Make shebang.- All seven required targets exist, including no-op branches where necessary.
buildreaches the architecture-specific and architecture-independent build work.- Each binary target reaches its corresponding build target.
- Build targets work without elevated privileges, and cleanup has been tested with its destructive scope understood.
- A dry run shows the intended dependency order before any real package build is attempted.