There’s an
excellent guide
explaining how to create a Debian package. That is, how to go from upstream
sources to an installable .deb, figuring out building tools, package
meta-information, and Debian packaging helper tools. This is hard enough as
it is, and it grind my gears when people ask «how hard can it be». I pity
the fool.
Now, after you think you understand the process, you find out there
is a proper way to build Debian Packages. It is thoroughly explained by the
Debian Policy Manual. That is,
how to ensure that your work, as a maintainer, keeps up with all quality assurance
and modern assumptions as prescribed by the Debian Project. Just because
you are able to build a .deb does not mean it will cooperate with
a working system, much less the whole ecosystem. I pity the
overconfident fool.
One of the requirements is for the package to build cleanly and repeatedly
from an empty chroot, having
to install all dependencies and tooling, as listed by the package description
you wrote. This helps the package maintainer confidently confirm, among
other things, that all dependencies are properly listed, as well
as complete and correct building instructions, ensuring the process is
independent from the developer’s setup. This is mandatory if you intend
to upload your packages to Debian’s package repository, and quite important if
you want to have a proper private APT repository to deploy them from.
I’m always creating custom Debian packages: backporting newer versions to
current Debian stable, packaging private software for easy distribution,
or amending packages to adapt to changing environments. More often
than not, I find myself packaging some internal piece of software A
that requires newer PostgreSQL libraries, and then having to package
a different internal piece of software B that depends on said package A.
Both need to be built inside empty chroots, A requiring specific
PostgreSQL libraries, and B requiring A being available as a
dependency. Creating the actual package takes time, but the hardest part
is figuring out how to build a package using «vanilla» Debian extended
with additional packages coming from other sources, including my own
APT sources.
…and I need to do this for multiple Debian versions, to ensure a smooth upgrade path.
Copy-on-Write (COW) building
pbuilder(8) is the core tool
for building Debian packages. It helps create a minimal «base» environment under
/var/cache/pbuilder/base.tgz. In order to build a package, base.tgz is
extracted into a working directory, the package sources extract there, and
then built inside a chroot(8), with automatic cleanup happening afterwards.
It is straightforward to use, but it requires a lot of I/O and disk space,
not to mention that building the «base» is simple only if you’re using
vanilla Debian APT repositories.
cowbuilder(8) is a wrapper around pbuilder(8) requiring less I/O
to work: instead of a file, it creates a «base»
filesystem hierarchy under /var/cache/pbuilder/base.cow. In order to build
a package, hardlinks (think cp -al) are used to create a working
directory, to then build using chroot(8). Both creating the work area
(«copy on write») and cleanup require much less I/O.
There are tradeoffs, as usual: pbuilder(8) affords more complex setups
and the ability to use tmpfs for speed, but cowbuilder(8) allows
fudging with the «base» environment to add things that are difficult
or impossible with the standard pbuilder flow.
This guide describes a cowbuilder «base» environment setup
including local changes to use PGDG packages, and packages that aren’t
even available via network APT. This same strategy can be applied
to create a «base» environment with multiple APT repos, or to create
multiple «base» environments targeting different Debian releases and
additional package combinations.
Working space and privileges
It’s better to have a dedicated partition to isolate pbuilder’s workspace.
Using an LVM volume allows adding space dynamically, and XFS is my filesystem
of choice for high-bandwith I/O. After amending /etc/fstab accordingly, these
will do the trick
# lvcreate --name pb --size 4g /dev/sys
# mkfs.xfs /dev/sys/pb
# grep pbuilder /etc/fstab
/dev/mapper/sys-pb /var/cache/pbuilder xfs defaults 0 1
# mount /var/cache/pbuilder/Installing cowbuilder will result in the default workspace
being set up
# apt install cowbuilder
# ls /var/cache/pbuilder/
aptcache
build
ccache
pbuildd
pbuilder-mnt
resultIn order for my regular user (emhn) to use cowbuilder directly, and pbuilder
indirectly, it helps having a local sudo configuration like this
# cat /etc/sudoers.d/pbuilder
Cmd_Alias PBUILDER = /usr/sbin/pbuilder, /usr/sbin/cowbuilder
emhn ALL = SETENV: NOPASSWD: PBUILDER«Vanilla» environment
Creation of pbuilder environments is controlled by text configuration
files. There is a central configuration file at /etc/pbuilderrc and a
per-user configuration in ~/.pbuilderrc, you should use when using
plain pbuilder. These are not enough for our purposes.
I have multiple pbuilder configuration files under my home directory.
They all start from a «vanilla» configuration using only standard Debian
APT sources. This is what I have for Debian bookworm
$ cat ~/etc/pb/pbuilderrc.bookworm-pg
MIRRORSITE=http://deb.debian.org/debian
DISTRIBUTION=bookworm
COMPONENTS='main contrib non-free non-free-firmware'
OTHERMIRROR0='deb http://deb.debian.org/debian bookworm-updates main contrib non-free non-free-firmware'
OTHERMIRROR1='deb http://security.debian.org/debian-security bookworm-security main contrib non-free non-free-firmware'
OTHERMIRROR="$OTHERMIRROR0|$OTHERMIRROR1"
EXTRAPACKAGES="apt-utils ca-certificates"
APTCACHE=/var/cache/pbuilder/aptcache-bookworm-pg
BASEPATH=/var/cache/pbuilder/base-bookworm-pg.cow
PDEBUILD_PBUILDER=cowbuilder
USE_PDEBUILD_INTERNAL=yes
DEBBUILDOPTS="-sa"The above contents should be self-explanatory in terms of APT sources,
distribution name, and components. Go read pbuilder(8) and pdebuild(1)
if you’re confused. Note the specific APTCACHE and BASEPATH directories so
that this environment has its own cache and base COW space. Each of my
multiple configurations sports different APT cache and COW spaces, so I can
build on different target distributions. There are some
EXTRAPACKAGES that get installed in the base: I’ve added
apt-utils and ca-certificates as they are needed in order to work
with non-Debian APT repositories as you’ll soon realize. Try to keep this
list at a minimum, as this is not the place to put generic package dependencies
(i.e. don’t put gcc here).
With this configuration file in place, we create the dedicated APT cache location
# mkdir /var/cache/pbuilder/aptcache-bookwrom-pg
# chmod 750 /var/cache/pbuilder/aptcache-bookwrom-pgand then create the initial COW base via sudo. This will take
a few minutes depending on your download and disk I/O speeds
$ sudo cowbuilder --create --config /home/emhn/etc/pb/pbuilderrc.bookworm-pg
I: Invoking pbuilder
I: forking: pbuilder create --configfile /home/emhn/etc/pb/pbuilderrc.bookworm-pg --buildplace /var/cache/pbuilder/base-bookworm-pg.cow --mirror http://deb.debian.org/debian --distribution bookworm --no-targz --extrapackages 'apt-utils ca-certificates cowdancer'
W: /root/.pbuilderrc does not exist
I: Running in no-targz mode
I: Distribution is bookworm.
I: Current time: Wed Nov 26 11:36:04 PST 2025
I: pbuilder-time-stamp: 1764185764
I: Building the build environment
I: running debootstrap
/usr/sbin/debootstrap
I: Target architecture can be executed
I: Retrieving InRelease
I: Checking Release signature
I: Valid Release signature (key id 4D64FEC119C2029067D6E791F8D2585B8783D481)
I: Retrieving Packages
I: Validating Packages
I: Resolving dependencies of required packages...
I: Resolving dependencies of base packages...
I: Checking component main on http://deb.debian.org/debian...
I: Retrieving adduser 3.134
I: Validating adduser 3.134
(...)
I: Retrieving zlib1g 1:1.2.13.dfsg-1
I: Validating zlib1g 1:1.2.13.dfsg-1
I: Chosen extractor for .deb packages: dpkg-deb
I: Extracting adduser...
(...)
I: Extracting zlib1g...
I: Installing core packages...
I: Unpacking required packages...
I: Unpacking adduser...
(...)
I: Unpacking zlib1g:amd64...
I: Configuring required packages...
(...)
I: Unpacking the base system...
(...)
I: Configuring the base system...
(...)
I: Base system installed successfully.
I: debootstrap finished
I: copying local configuration
W: No local /etc/mailname to copy, relying on /var/cache/pbuilder/base-bookworm-pg.cow/etc/mailname to be correct
I: Installing apt-lines
I: Refreshing the base.tgz
I: upgrading packages
I: mounting /proc filesystem
I: mounting /sys filesystem
I: creating /{dev,run}/shm
I: mounting /dev/pts filesystem
I: redirecting /dev/ptmx to /dev/pts/ptmx
I: installing dummy policy-rc.d
Get:1 http://deb.debian.org/debian bookworm-updates InRelease [55.4 kB]
Get:2 http://security.debian.org/debian-security bookworm-security InRelease [48.0 kB]
Hit:3 http://deb.debian.org/debian bookworm InRelease
Get:4 http://deb.debian.org/debian bookworm-updates/main amd64 Packages [6924 B]
Get:5 http://deb.debian.org/debian bookworm/non-free-firmware amd64 Packages [6368 B]
Get:6 http://deb.debian.org/debian bookworm/contrib amd64 Packages [53.5 kB]
Get:7 http://deb.debian.org/debian bookworm/non-free amd64 Packages [102 kB]
Get:8 http://security.debian.org/debian-security bookworm-security/non-free-firmware amd64 Packages [688 B]
Get:9 http://security.debian.org/debian-security bookworm-security/main amd64 Packages [288 kB]
Get:10 http://security.debian.org/debian-security bookworm-security/contrib amd64 Packages [896 B]
Fetched 561 kB in 1s (481 kB/s)
Reading package lists...
I: Obtaining the cached apt archive contents
(...)
Reading package lists...
Building dependency tree...
Calculating upgrade...
The following packages will be upgraded:
libssl3 linux-libc-dev
2 upgraded, 0 newly installed, 0 to remove and 0 not upgraded.
Need to get 4232 kB of archives.
(...)
Reading package lists...
Building dependency tree...
Reading state information...
build-essential is already the newest version (12.9).
dpkg-dev is already the newest version (1.21.22).
The following additional packages will be installed:
aptitude-common libboost-iostreams1.74.0 libcwidget4 libncursesw6
libsigc++-2.0-0v5 libsqlite3-0 libxapian30 openssl
Suggested packages:
apt-xapian-index aptitude-doc-en | aptitude-doc debtags tasksel
libcwidget-dev xapian-tools
Recommended packages:
sensible-utils libgpm2
The following NEW packages will be installed:
apt-utils aptitude aptitude-common ca-certificates cowdancer
libboost-iostreams1.74.0 libcwidget4 libncursesw6 libsigc++-2.0-0v5
libsqlite3-0 libxapian30 openssl
0 upgraded, 12 newly installed, 0 to remove and 0 not upgraded.
Need to get 7725 kB of archives.
(...)
I: Copying back the cached apt archive contents
I: new cache content 'libssl3_3.0.17-1~deb12u3_amd64.deb' added
(...)
I: unmounting dev/ptmx filesystem
I: unmounting dev/pts filesystem
I: unmounting dev/shm filesystem
I: unmounting proc filesystem
I: unmounting sys filesystemLooking at the above output, note:
Working in non-targz mode, using the desired distribution (
bookworm), on the dedicated COW location.After the base system is created by
debootstrap, all APT sources were downloaded. There were security patches, applied immediately.Requested extra packages being installed alongside their dependencies.
Once the base is built, new directories appear in place
# ls /var/cache/pbuilder/
aptcache
aptcache-bookworm-pg <--- Dedicated APT cache
base-bookworm-pg.cow <--- COW base
build
ccache
pbuildd
pbuilder-mnt
resultThe dedicated cache is there to store any packages you download when updating
or building packages using this cowbuilder configuration. You may clean the
dedicated cache using rm to reclaim space. The next time you update the
COW builder, or you build a package using it, packages will be downloaded
again. The more packages you build, the larger the cache, and the less things
you need to download when building packages.
Avoid fiddling with the COW base unless you know exactly what you are doing. If you break it, it’s better to create it from scratch and start over.
Updating the environment
If you create a «vanilla» environment for Debian unstable, you’d want
to update it daily in order to keep track with Debian development. If you
create a «vanilla» environment for Debian bookworm, you’d want to update
it every time there are security patches or point releases, to ensure the
COW contains the latest avaiable packages for the target.
You should be able to update at any time and as many times as you want
$ sudo cowbuilder --update --config ~/etc/pb/pbuilderrc.bookworm-pg
I: Copying COW directory
I: forking: rm -rf /var/cache/pbuilder/build/cow.70935
I: forking: cp -al /var/cache/pbuilder/base-bookworm-pg.cow /var/cache/pbuilder/build/cow.70935
I: removed stale ilistfile /var/cache/pbuilder/build/cow.70935/.ilist
I: Invoking pbuilder
I: forking: pbuilder update --configfile /home/emhn/etc/pb/pbuilderrc.bookworm-pg --buildplace /var/cache/pbuilder/build/cow.70935 --mirror http://deb.debian.org/debian --distribution bookworm --extrapackages 'apt-utils ca-certificates' --no-targz --internal-chrootexec 'chroot /var/cache/pbuilder/build/cow.70935 cow-shell'
I: Running in no-targz mode[0m
I: Current time: Wed Nov 26 16:04:41 PST 2025[0m
I: pbuilder-time-stamp: 1764201881[0m
I: copying local configuration[0m
I: mounting /proc filesystem[0m
I: mounting /sys filesystem[0m
I: creating /{dev,run}/shm[0m
I: mounting /dev/pts filesystem[0m
I: redirecting /dev/ptmx to /dev/pts/ptmx[0m
I: policy-rc.d already exists[0m
I: Refreshing the base.tgz [0m
I: upgrading packages[0m
Hit:1 http://security.debian.org/debian-security bookworm-security InRelease
Hit:2 http://deb.debian.org/debian bookworm-updates InRelease
Hit:3 http://deb.debian.org/debian bookworm InRelease
Reading package lists...
I: Obtaining the cached apt archive contents[0m
Reading package lists...
Building dependency tree...
Reading state information...
Calculating upgrade...
0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded.
Reading package lists...
Building dependency tree...
Reading state information...
0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded.
Reading package lists...
Building dependency tree...
Reading state information...
build-essential is already the newest version (12.9).
dpkg-dev is already the newest version (1.21.22).
apt-utils is already the newest version (2.6.1).
ca-certificates is already the newest version (20230311+deb12u1).
aptitude is already the newest version (0.8.13-5).
0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded.
I: Copying back the cached apt archive contents[0m
I: unmounting dev/ptmx filesystem[0m
I: unmounting dev/pts filesystem[0m
I: unmounting dev/shm filesystem[0m
I: unmounting proc filesystem[0m
I: unmounting sys filesystem[0m
I: removing cowbuilder working copy
I: Moving work directory [/var/cache/pbuilder/build/cow.70935] to final location [/var/cache/pbuilder/base-bookworm-pg.cow] and cleaning up old copy
I: forking: rm -rf /var/cache/pbuilder/build/cow.70935-70935-tmpLooking at the above output, note:
Working in non-targz mode, using the desired distribution (
bookworm), on the dedicated COW location.All APT sources were downloaded. If there are any security patches (and there weren’t in this example), they should be applied immediately.
The update is done over a working directory (
cow.70935above). If the update is successful, it will replace the old copy as the new default COW base.
At this point, we have a «vanilla» Debian bookworm cowbuilder environment,
fully patched, easy to update, where to build packages.
Let’s fiddle with the COW base. I know what I’m doing.
Supporting PGDG packages
Several of my packaging effors require access to every supported PostgreSQL version, or PostgreSQL adjacent packages that are not part of Debian. The PostgresSQL Global Development Group maintains an APT repository providing those, and I need the COW environment to be able to install those packages on demand.
This is precisely why having a COW directory is better than having a
COW base.tgz. Working inside the COW directory I download the
PGDG APT repository GPG key and place it in the proper location
# cd /var/cache/pbuilder/aptcache-bookworm-pg/etc/apt/trusted.gpg.d/
# curl -s -o apt.postgresql.org.asc https://www.postgresql.org/media/keys/ACCC4CF8.ascThen, manually include the APT source alongside the standard Debian ones, so the list looks like this
# cat /var/cache/pbuilder/aptcache-bookworm-pg/etc/apt/sources.list
deb http://deb.debian.org/debian bookworm-updates main contrib non-free non-free-firmware
deb http://security.debian.org/debian-security bookworm-security main contrib non-free non-free-firmware
deb http://deb.debian.org/debian bookworm main contrib non-free non-free-firmware
deb http://apt.postgresql.org/pub/repos/apt bookworm-pgdg mainFinally, add the extra PGDG APT source to the pbuilder configuration file
$ cat ~/etc/pb/pbuilderrc.bookworm-pg
(...)
OTHERMIRROR2='deb http://apt.postgresql.org/pub/repos/apt/ bookworm-pgdg main'
OTHERMIRROR="$OTHERMIRROR0|$OTHERMIRROR1|$OTHERMIRROR2"
(...)Updating the environment results in the PGDG APT package list being downloaded
$ sudo cowbuilder --update --config ~/etc/pb/pbuilderrc.bookworm-pg
I: Copying COW directory
I: forking: rm -rf /var/cache/pbuilder/build/cow.73768
I: forking: cp -al /var/cache/pbuilder/base-bookworm-pg.cow /var/cache/pbuilder/build/cow.73768
I: removed stale ilistfile /var/cache/pbuilder/build/cow.73768/.ilist
I: Invoking pbuilder
I: forking: pbuilder update --configfile /home/emhn/etc/pb/pbuilderrc.bookworm-pg --buildplace /var/cache/pbuilder/build/cow.73768 --mirror http://deb.debian.org/debian --distribution bookworm --extrapackages 'apt-utils ca-certificates' --no-targz --internal-chrootexec 'chroot /var/cache/pbuilder/build/cow.73768 cow-shell'
(...)
I: upgrading packages
Hit:1 http://security.debian.org/debian-security bookworm-security InRelease
Hit:2 http://deb.debian.org/debian bookworm-updates InRelease
Hit:3 http://deb.debian.org/debian bookworm InRelease
Get:4 http://apt.postgresql.org/pub/repos/apt bookworm-pgdg InRelease [107 kB]
Get:5 http://apt.postgresql.org/pub/repos/apt bookworm-pgdg/main amd64 Packages [404 kB]
Fetched 511 kB in 2s (251 kB/s)
Reading package lists...
I: Obtaining the cached apt archive contents
Reading package lists...
Building dependency tree...
Reading state information...
Calculating upgrade...
0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded.
(...)
I: Moving work directory [/var/cache/pbuilder/build/cow.73768] to final location [/var/cache/pbuilder/base-bookworm-pg.cow] and cleaning up old copy
I: forking: rm -rf /var/cache/pbuilder/build/cow.73768-73768-tmpConfirming the PGDG APT sources are fully integrated into the
COW base environment, I can extend the configuration to add any default
packages that are convenient when working with PGDG. Again, this is not
to add build dependencies. For PGDG, I like to add postgresql-common
as it provides the latest pg_buildext, bringing more functionality
than the standard one provided by «vanilla» Debian
$ cat ~/etc/pb/pbuilderrc.bookworm-pg
(...)
EXTRAPACKAGES="apt-utils ca-certificates postgresql-common"
(...)Update the COW base once more
$ sudo cowbuilder --update --config ~/etc/pb/pbuilderrc.bookworm-pg
I: Copying COW directory
I: forking: rm -rf /var/cache/pbuilder/build/cow.74631
I: forking: cp -al /var/cache/pbuilder/base-bookworm-pg.cow /var/cache/pbuilder/build/cow.74631
I: removed stale ilistfile /var/cache/pbuilder/build/cow.74631/.ilist
I: Invoking pbuilder
I: forking: pbuilder update --configfile /home/emhn/etc/pb/pbuilderrc.bookworm-pg --buildplace /var/cache/pbuilder/build/cow.74631 --mirror http://deb.debian.org/debian --distribution bookworm --extrapackages 'apt-utils ca-certificates postgresql-common' --no-targz --internal-chrootexec 'chroot /var/cache/pbuilder/build/cow.74631 cow-shell'
(...)
The following additional packages will be installed:
libjson-perl netbase postgresql-client-common sensible-utils ssl-cert ucf
Recommended packages:
libjson-xs-perl logrotate
The following NEW packages will be installed:
libjson-perl netbase postgresql-client-common postgresql-common
sensible-utils ssl-cert ucf
0 upgraded, 7 newly installed, 0 to remove and 0 not upgraded.
Need to get 371 kB of archives.
(...)
Preparing to unpack .../6-postgresql-common_287.pgdg12+1_all.deb ...
Adding 'diversion of /usr/bin/pg_config to /usr/bin/pg_config.libpq-dev by postgresql-common'
Unpacking postgresql-common (287.pgdg12+1) ...
(...)
Setting up postgresql-common (287.pgdg12+1) ...
Creating config file /etc/postgresql-common/createcluster.conf with new version
Building PostgreSQL dictionaries from installed myspell/hunspell packages...
I: Copying back the cached apt archive contents
(...)
I: new cache content 'postgresql-client-common_287.pgdg12+1_all.deb' added
I: new cache content 'postgresql-common_287.pgdg12+1_all.deb' added
(...)
I: Moving work directory [/var/cache/pbuilder/build/cow.74631] to final location [/var/cache/pbuilder/base-bookworm-pg.cow] and cleaning up old copy
I: forking: rm -rf /var/cache/pbuilder/build/cow.74631-74631-tmpLooking at the above output, note:
Working in non-targz mode, using the desired distribution (
bookworm), on the dedicated COW location.All APT sources were downloaded. The additional
postgresql-commonpackage was downloaded from PGDG, installed and configured.The update is done over a working directory (
cow.74631above). The update was successful, so it will replace the old copy as the new default COW base.
This completes the creation of a Debian bookworm COW base that includes
PGDG repositories, suitable for building PostgreSQL-related packages targeting
Debian 12.
Building a package using this base
If you have a properly created Debian package source, you can use this
custom COW base via the traditional pdebuild(1) front end. It amounts to
$ cd ~/src/packages/a-package-source/
$ pdebuild --configfile /home/emhn/etc/pb/pbuilderrc.bookworm-pgDetails on how to properly configure and fine-tune pdebuild are beyond the
scope of this guide. I prefer to have a shell alias defined like so in
~/.bash_aliases
alias pdebop='DEB_BUILD_OPTIONS=nocheck pdebuild --use-pdebuild-internal --configfile /home/emhn/etc/pb/pbuilderrc.bookworm-pg'and then simply
$ cd ~/src/packages/a-package-source/
$ pdebopUnsurprisingly, I have a dozen aliases for the multiple targets I package for.
Additional APT sources or packages
In order to build packages using additional non-Debian APT sources, repeat these steps for every extra non-Debian APT source:
Download the corresponding APT key for the source, and place it under
/etc/apt/trusted.gpg.d/inside the COW base.Add the corresponding APT source to
/etc/apt/sources.listinside the COW base.Extend
OTHERMIRRORaccordingly in thepbuilderconfiguration file.Verify you can update COW base, pulling the package list from the newly added APT repository.
If needed, extend
EXTRAPACKAGES, and update COW base again.
This works with any properly built APT repository, public or private. I’ve used it to pull packages from PGDG, Debian Backports, or from the internal APT repository.
Sometimes, I’m working on a sequence of packages that are not part of a repository. Consider a Perl library P1 that depends on a Perl library P0 and a C library C0 to be available for building, and none of them have Debian packages anywhere. I have to package P0 and C0 first, before I can work on packaging P1. There’s usually a back and forth until I figure proper packaging out, so it’s unreasonable to upload partially complete C0 and P0 packages to a private APT repository. Instead, they will be sitting on my local system, and I would like to use them during builds.
My packaging work happens under ~/src/packages/<package-names>. It’s
possible to extend ~/etc/pb/pbuilderrc.bookworm-pg with an environment
variable so that every time a package is successfully built using pdebuild,
the resulting* .deb and associated metainformation files are placed
under ~/src/packages/result. A configuration like this
$ cat ~/etc/pb/pbuilderrc.bookworm-pg
(...)
export BUILDRESULT=/home/emhn/src/packages/result
OTHERMIRROR="$OTHERMIRROR|deb [trusted=yes] file://$BUILDRESULT ./"
(...)after the corresponding manual adjustment to sources.list inside
the COW base, would work great if ~/src/packages/result looks like
a proper local APT repository.
Package apt-utils includes apt-ftparchive(1), a command line tool that
generates the index files APT needs to access a distribution source. An
effective workflow for cowbuilder would be to regenerate these index
files under ~/src/packages/result just after preparing the working
COW environment, but before updating the list of available packages
for installation via APT once the build starts. This is one of the
many use cases for «pbuilder hooks»: custom scripts that are run
at different stages during the COW create or update phases.
To address this particular scenario, I wrote a shell script
~/etc/pb/hooks/H05deps including, among other things,
#!/bin/sh
if [ -n "$BUILDRESULT" ]
then
echo HOOK: Refreshing $BUILDRESULT repository
cd $BUILDRESULT
apt-ftparchive packages . > Packages
else
echo HOOK: Set BUILDRESULT to use a local repository
fiThe name is chosen so that the script is executed after unpacking
the COW chroot, mounting /proc and bind-mounts, but before any
other operations. Refreshing at this point will ensure ~/src/packages/result/
looks like a proper local APT source. Moreover, since the repository
is listed in COW sources.list as [trusted=yes], the build routine
will be able to apt install packages from there without GPG keys.
In order for the hook to be considered, I added
$ cat ~/etc/pb/pbuilderrc.bookworm-pg
(...)
HOOKSDIR="/home/emhn/etc/pb/hooks"
(...)Recall each build process runs inside the updated COW using
chroot(1) over a temporary location below /var/cache/pbuilder/.
But ~/src/package/result/ lives below /home/, absolutely
invisible to the chroot’ed environment. In order for pbuilder(1)
to access the local repository while chroot’ed, I need to leverage
«bind mounts», a Linux capability allowing to have alternative views
for a particular directory. pbuilder(1) will bind mount directories
so they become accesible inside the chroot
$ cat ~/etc/pb/pbuilderrc.bookworm-pg
(...)
BINDMOUNTS="$BINDMOUNTS $BUILDRESULT"
(...)With this configuration in place
$ sudo cowbuilder update --configfile ~/etc/pb/pbuilderrc.bookworm-pg
(...)
I: Mounting /home/emhn/src/packages/result
(...)
I: user script /var/cache/pbuilder/build/cow.117136/tmp/hooks/H05deps starting
HOOK: Refreshing /home/emhn/src/packages/result repository
I: user script /var/cache/pbuilder/build/cow.117136/tmp/hooks/H05deps finished
I: Refreshing the base.tgz
I: upgrading packages
(...)
Get:3 file:/home/emhn/src/packages/result ./ Packages [965 B]
Hit:4 http://deb.debian.org/debian bookworm-updates InRelease
Hit:5 http://deb.debian.org/debian bookworm InRelease
Hit:6 http://security.debian.org/debian-security bookworm-security InRelease
Hit:7 http://apt.postgresql.org/pub/repos/apt bookworm-pgdg InRelease
(...)
I: unmounting /home/emhn/src/packages/result filesystem
(...)
I: Moving work directory [/var/cache/pbuilder/build/cow.117136] to final location [/var/cache/pbuilder/base-bookworm-pg.cow] and cleaning up old copy
I: forking: rm -rf /var/cache/pbuilder/build/cow.117136-117136-tmpLooking at the above output, note:
The local package directory was bind-mounted inside the
chroot.The hook script was run. Notice how it was copied inside the
chrootbefore running it, after the bind-mount was completed.The local packate repository list was read correctly, making those packages available for local builds.
This setup allows me to work on packaging by having partially correct
packages under ~/src/packages/result/. Once I’m confident packages are
properly built, passing multiple Debian Quality Assurance tests such
as lintian(1), I upload them to a proper network APT repository, and
clean up ~/src/packages/result/.
Conclusion
Some people can get away with
$ fakeroot debian/rules binaryto produce a .deb they can manually install using dpkg. I’m sure
«it works on my machine» will give these people a warm fuzzy feeling,
but it is a far cry from proper Debian package building for
consistent deployment and upgrades.
The scaffolding technique described in this guide has served me for over two decades to contribute back to the Debian Project, as well as building private packages that literally help several TLDs operate properly both at the database as well as the DNS levels.
I hope others find it helpful to come up with their own workflow.