How to set up for Ubuntu development

The following is a short guide to getting set up for Ubuntu development.

Prerequisites

You must have a Launchpad ID. To get an ID:

Install software

$ sudo apt update && \
  sudo apt dist-upgrade -y && \
  sudo apt install -y \
    autopkgtest \
    dh-make \
    git-buildpackage \
    pastebinit \
    ubuntu-dev-tools && \
  sudo snap install lxd && \
  sudo snap install --classic snapcraft && \
  sudo snap install --classic git-ubuntu

Configure your groups

Your user should be a member of the following groups:

  • adm

  • libvirt

  • lxd

  • sbuild

  • sudo

Ensure you have installed the packages listed above, which will be the trigger to create most of these groups. For group membership to be activated one usually needs to re-login. Then, one can double check group membership via:

$ groups my_user
my_user : my_user adm cdrom sudo dip plugdev lpadmin sambashare \
 libvirt sbuild lxd

If any of the following groups is missing for your user you can fix it via adduser, like this:

$ sudo adduser my_user lxd
$ sudo adduser my_user sbuild
$ sudo adduser my_user libvirt

Configure software

GnuPG

GnuPG is an encryption tool that helps manage your encryption keys. You’ll need it later to be able to add a signature to each upload.

Eventually the key will represent your identity. Therefore it needs to fulfil several recommendations and be kept safe as well as out of reach of other entities. The setup can be quite complex and is outlined step by step in Set up and manage PGP keys.

Git

Installing git-ubuntu will modify your .gitconfig. Make sure it got your Launchpad username correct:

[gitubuntu]
    lpuser = your-launchpad-username

You must also ensure that the [user] section has your name and email:

[user]
    name = Your Full Name
    email = your@email.com

You may also want to add the following to your .gitconfig:

[log]
    decorate = short
[commit]
    verbose = true
[merge]
    summary = true
    stat = true
[core]
    whitespace = trailing-space,space-before-tab

[diff "ruby"]
    funcname = "^ *\\(\\(def\\) .*\\)"
[diff "image"]
    textconv = identify

[url "git+ssh://my_lp_username@git.launchpad.net/"]
    insteadof = lp:

Quilt

Quilt is a CLI used to manage patch stacks. It can take any number of patches and condense them into a single patch.

A working .quiltrc:

d=. ; while [ ! -d $d/debian -a `readlink -e $d` != / ]; do d=$d/..; done
if [ -d $d/debian ] && [ -z $QUILT_PATCHES ]; then
    # if in Debian packaging tree with unset $QUILT_PATCHES
    QUILT_PATCHES="debian/patches"
    QUILT_PATCH_OPTS="--reject-format=unified"
    QUILT_DIFF_ARGS="-p ab --no-timestamps --no-index --color=auto"
    QUILT_REFRESH_ARGS="-p ab --no-timestamps --no-index"
    QUILT_COLORS="diff_hdr=1;32:diff_add=1;34:diff_rem=1;31:diff_hunk=1;33:diff_ctx=35:diff_cctx=33"
    if ! [ -d $d/debian/patches ]; then mkdir $d/debian/patches; fi
fi

This configures Quilt for use with Debian packages, with default settings that conform to standard Debian practices.

dput-ng

dput-ng (the Debian Package Upload Tool, next generation) is the modern replacement for dput. It’s used to upload a software package to the Ubuntu repository, or to a personal package archive (PPA).

On recent Ubuntu releases, dput-ng is provided by the dput package, so installing dput gives you dput-ng automatically:

$ sudo apt install dput

A working .dput.cf:

[DEFAULT]
default_host_main = unspecified

[unspecified]
fqdn = SPECIFY.A.TARGET
incoming = /

[ppa]
fqdn            = ppa.launchpad.net
method          = ftp
incoming        = ~%(ppa)s/ubuntu

This configures dput for safety, such that if you accidentally forget to specify a destination, it’ll default to doing nothing.

sbuild

sbuild is the recommended tool for building packages on Ubuntu. It supports two backends: unshare (recommended) and schroot (needed for cross-building without unshare).

The unshare backend uses user namespaces and mmdebstrap to create isolated build environments without requiring root or schroot setup. With recent versions of sbuild, chroot tarballs are created and managed automatically on demand using mmdebstrap, making the unshare backend the recommended approach for most users.

This automatic chroot management is available out of the box on Ubuntu 25.10 and later. It is also available on Ubuntu 24.04 LTS when sbuild is installed from the noble-backports pocket. It is not available on Ubuntu 22.04 LTS and earlier – use the schroot backend setup instead.

Note

On Ubuntu 24.04 LTS, the noble-backports pocket is enabled by default in /etc/apt/sources.list.d/ubuntu.sources. If you have disabled it, re-enable it before installing sbuild from backports.

Install sbuild (from backports on 24.04 LTS) together with mmdebstrap and uidmap:

Ubuntu 25.10 and later:

$ sudo apt install -y sbuild mmdebstrap uidmap

Ubuntu 24.04 LTS:

$ sudo apt install -y -t noble-backports sbuild mmdebstrap uidmap

sbuild reads the user specific configuration file ~/.config/sbuild/config.pl.

Create the file if it does not exist:

$ mkdir -p ~/.config/sbuild
$ touch ~/.config/sbuild/config.pl

Save the file with the following content:

$chroot_mode = 'unshare';
$unshare_mmdebstrap_keep_tarball = 1;

$unshare_tmpdir_template = '/var/tmp/tmp.sbuild.XXXXXXXXXX';

$clean_source = 0;
$run_lintian = 0;

The schroot backend is the traditional approach. Use this if you are on Ubuntu 22.04 LTS or earlier, or if you need to cross-build packages without unshare (see How to build packages locally).

Make the required mount points for builds, logs, and scratch:

$ mkdir -p ~/schroot/{build,logs,scratch}

Add a scratch directory to /etc/schroot/sbuild/fstab:

$ echo "$HOME/schroot/scratch  /scratch          none  rw,bind  0  0" \
  | sudo tee -a /etc/schroot/sbuild/fstab

Optionally, you can mount your home directory inside the container:

$ echo "$HOME  $HOME          none  rw,bind  0  0" \
  | sudo tee -a /etc/schroot/sbuild/fstab

sbuild reads the user specific configuration file ~/.config/sbuild/config.pl.

Create the file if it does not exist:

$ mkdir -p ~/.config/sbuild
$ touch ~/.config/sbuild/config.pl

Save the file with the following content, replacing the placeholders:

  • $maintainer_name = 'Your Full Name <your@email.com>';

  • $build_dir = '/home/my_user/schroot/build';

  • $log_dir = "/home/my_user/schroot/logs";

# Name to use as override in .changes files for the Maintainer: field
# (optional; only uncomment if needed).
# $maintainer_name = 'Your Full Name <your@email.com>';

$chroot_mode = 'schroot';
$unshare_mmdebstrap_keep_tarball = 1;

# Default distribution to build.
$distribution = "resolute";
# Build arch-all by default.
$build_arch_all = 1;

# Do not check for the presence of the build dependencies on the host
# system, as these exist only in the unshare chroot.
$clean_source = 0;
$run_lintian = 0;

# When to purge the build directory afterwards; possible values are 'never',
# 'successful', and 'always'.  'always' is the default. It can be helpful
# to preserve failing builds for debugging purposes.  Switch these comments
# if you want to preserve even successful builds, and then use
# 'schroot -e --all-sessions' to clean them up manually.
$purge_build_directory = 'successful';
$purge_session = 'successful';
$purge_build_deps = 'successful';

# Directory for chroot symlinks and sbuild logs.  Defaults to the
# current directory if unspecified.
$build_dir = '/home/my_user/schroot/build';

# Directory for writing build logs to
$log_dir = '/home/my_user/schroot/logs';

# Key used to sign the source package. Defaults to not using any key.
# $key_id = '';

# don't remove this, Perl needs it:
1;

Create ~/.mk-sbuild.rc:

$ touch ~/.mk-sbuild.rc

Save the file with the following content:

SCHROOT_CONF_SUFFIX="source-root-users=root,sbuild,admin
source-root-groups=root,sbuild,admin
preserve-environment=true"
# you will want to undo the below for stable releases, read `man mk-sbuild` for details
# during the development cycle, these pockets are not used, but will contain important
# updates after each release of Ubuntu
SKIP_UPDATES="1"
SKIP_PROPOSED="1"
# if you have a local proxy like apt-cacher-ng around enable the following
# DEBOOTSTRAP_PROXY=http://127.0.0.1:3142/

schroots

Having sbuild set up is only half of the solution - schroot (secure chroot) environments for the respective builds are also needed.

Get a schroot for a specific release of Ubuntu using mk-sbuild:

$ mk-sbuild resolute --arch=amd64

List the available schroots:

$ sbuild -l

Update a schroot:

$ sbuild-update -udc resolute-amd64

Delete a schroot:

$ sudo rm /etc/schroot/chroot.d/sbuild-resolute-amd64
$ sudo rm -rf /var/lib/schroot/chroots/resolute-amd64

LXD

LXD is a powerful container system similar in concept to Docker and other container software.

Install and set up LXD using the standard installation directions.

Create some helper aliases for common LXD tasks:

$ lxc alias add ls 'list -c ns4,user.comment:comment'

$ lxc alias add login 'exec @ARGS@ \
--mode interactive -- bash -xac $@my_user - exec /bin/login -p -f '

Note that the trailing space after the -f is important. Replace ‘my_user’ with ‘ubuntu’ or whatever username you use in your containers.

Note

For more info, see the LXD documentation

Caching packages

When building packages with tools like sbuild or autopkgtest, the build environment downloads packages from the Ubuntu mirrors on each run. This can amount to hundreds of megabytes per build and significantly slow down the iteration cycle.

Setting up a local package cache is strongly recommended.

apt-cacher-ng

The simplest approach is to install apt-cacher-ng on your development machine with its default configuration:

$ sudo apt install apt-cacher-ng

Then configure apt to use it by creating /etc/apt/apt.conf.d/01acng with:

Acquire::http { Proxy "http://127.0.0.1:3142"; }

See also the DEBOOTSTRAP_PROXY setting in .mk-sbuild.rc as an example.

auto-apt-proxy

auto-apt-proxy detects a cache on your local network automatically. This is useful if:

  • You have a shared cache on your LAN rather than your local machine.

  • You need the setup to work without reconfiguring when changing networks (e.g., at sprints or conferences).

$ sudo apt install auto-apt-proxy

Even with a local apt-cacher-ng, auto-apt-proxy can help resolve the correct address to use from inside VMs or containers.

Configure your .profile

Your .profile should include entries for DEBFULLNAME and DEBEMAIL:

export DEBFULLNAME="Your Full Name"
export DEBEMAIL=your@email.com

You can also set the DEBSIGN variables:

export DEBSIGN_PROGRAM="/usr/bin/gpg2"
export DEBSIGN_KEYID="0xMYKEYHASH"

A fix for “clear-sign failed: Inappropriate ioctl for device”:

$ export GPG_TTY=$(tty)

If you’re operating from a GUI, this can be useful:

$ eval `dbus-launch --sh-syntax`

Keyring with plaintext storage

See git-ubuntu Keyring integration for details on how git-ubuntu uses keyring. If you want to reconfigure keyring to use plaintext storage to avoid getting keyring password prompts, create the file ~/.local/share/python_keyring/keyringrc.cfg with the following contents:

[backend]
default-keyring=keyrings.alt.file.PlaintextKeyring