Building local packages
This guide takes you through a basic package build without setting up the full dispatcher build system. It is ideal for simple packages that do not have complex dependencies, and for when you don't want to set up a full build system. All you need is the pkg tool that is already present on your system.
If you do want the whole thing, with gates, review and automatic rebuilds, then Deploying the build system is the guide you want instead. There's also a video of a local build if you'd rather watch one first.
Here are the steps, and you'll want to walk them in this order:
- Before you start
- Write the recipe
- Create a build key
- Build the package
- Create an installable bundle
- Install the bundle
- Verify the installation
- What you end up with
Before you start
pkg build runs the recipe's install phase inside a user namespace with several UID ranges. That only works if your user has a subordinate UID/GID range. In most cases you can just give your user the standard range of 65536 IDs, starting at 100000:
$ echo "$USER:100000:65536" | sudo tee -a /etc/subuid /etc/subgid
$ cat /etc/subuid
<user>:100000:65536
$ cat /etc/subgid
<user>:100000:65536
The build also needs network access, to fetch the source tarball and the buildroot packages from the repositories configured in /etc/pkg/repos.d/.
Write the recipe
A recipe is a directory containing a recipe.toml, plus optional files that this guide doesn't cover. The [recipe] block gives the name, version and release. Each [[source]] gives an upstream tarball and its SHA-256, which pkg build checks for you.
The build system doesn't need to be declared. pkg build detects it, which is autotools here, and runs configure, make and make install for you.
$ mkdir hello
$ $EDITOR hello/recipe.toml
# GNU hello -- a first local pkg build.
[recipe]
name = "hello"
version = "2.12.3"
release = 1
description = "GNU hello -- the canonical example of a GNU package"
homepage = "https://www.gnu.org/software/hello/"
license_hint = "GPL-3.0-or-later"
summary = """
GNU hello prints a friendly greeting. It exists to show how a GNU package
is laid out and built, which also makes it a good first recipe.
"""
[[source]]
name = "hello"
url = "https://ftp.gnu.org/gnu/hello/hello-2.12.3.tar.gz"
sha256 = "0d5f60154382fee10b114a1c34e785d8b1f492073ae2d3a6f7b147687b366aa0"
unpack_to = "."
Create a build key
Everything that pkg build publishes is signed with your personal Ed25519 build key. pkg build won't create one for you, so make it once:
$ pkg keygen
wrote build key ~/.config/pkg/build.key
public key 1477cf8e47e283811c49692d0b7ab047d2ac053019eab10527a08143c1ba4998
Note the public key, and write it down somewhere. Any machine that installs your packages needs it to trust them, as you'll see when you install the bundle further on.
Build the package
$ pkg build ./hello --out out
Here's what pkg build does:
- it downloads the source and checks its SHA-256,
- it picks a build probe, autotools in this case,
- it composes a clean, signature-verified buildroot from the repo chain. It never uses the host's
/usrfor this, - it runs configure, build and install in a sandbox,
- it splits the installed files into subpackages,
- and it signs those and publishes them into the local repository
out/, which it creates on first use.
The build log goes to stderr, and it's about 2,200 lines of it here. These are the lines that matter:
autobuilder-core: materializing 1 source(s):
+ hello https://ftp.gnu.org/gnu/hello/hello-2.12.3.tar.gz sha256=0d5f6015...6aa0 -> .../build/. (stripped prefix hello-2.12.3/)
autobuilder-core: probe selected: autotools (applying: autotools)
build: building hello in sandbox
autobuilder-core: executing 1 build plan(s):
+ probe autotools:
configure: ... '/build/configure' '--prefix=/usr' '--sysconfdir=/etc' ... '--host=x86_64-pc-linux-gnu'
build: /usr/bin/make -j4
install: /usr/bin/make DESTDIR=/build/staging/autotools -j4 install
=== phase: configure ===
=== phase: build ===
=== phase: install ===
autobuilder-core: classified into 52 subpackage(s): bin, data, debug, debugsrc, info, lang/ast, ... lang/zh_TW, man
build: warning [license-aggregate-divergence]: askalono detected "GPL-3.0-only" but recipe license_hint
is "GPL-3.0-or-later"; the hint is authoritative -- reconcile if the scan is right
That license warning is harmless. GNU hello is GPLv3-or-later, and its own --version output says "GPLv3+", so the recipe's license_hint is correct.
The summary goes to stdout, with the lang/* lines shortened here:
published 52 subpackage(s) into <workdir>/out
hello/bin 2.12.3-1.x86_64
hello/data 2.12.3-1.noarch
hello/debug 2.12.3-1.x86_64
hello/debugsrc 2.12.3-1.noarch
hello/info 2.12.3-1.noarch
hello/lang/ast 2.12.3-1.noarch
... (46 more lang/* translation subpackages)
hello/lang/zh_TW 2.12.3-1.noarch
hello/man 2.12.3-1.noarch
trust anchor <workdir>/out/metadata/1.root.json (key 1477cf8e47e283811c49692d0b7ab047d2ac053019eab10527a08143c1ba4998)
The whole thing takes about 2 to 3 minutes, and most of that is spent composing the buildroot. The result is an ordinary signed repository of 744 KB:
$ ls out
data metadata pkgs
Create an installable bundle
A .pkgtar bundle is a single, self-describing, signed file. You can copy it to another machine, even an air-gapped one, and install it there without configuring any repository at all.
$ pkg bundle create --from-repo-dir out --output hello-2.12.3-1.pkgtar
wrote bundle: <workdir>/hello-2.12.3-1.pkgtar
$ du -h hello-2.12.3-1.pkgtar
340K hello-2.12.3-1.pkgtar
$ sha256sum hello-2.12.3-1.pkgtar
e9442071397a9eca3973a88528e1ed8758e30255b3f0f86973b808274bffe065 hello-2.12.3-1.pkgtar
The --from-repo-dir option temporarily puts the output repository on top of the repo chain, so you don't have to configure it as a repository first. Without a --closure the bundle contains everything the repository ships, so all 52 subpackages. On the inside it's a tar of content-addressed payloads plus signed TUF metadata, 107 entries in this case:
$ tar tf hello-2.12.3-1.pkgtar | head -3
data/04/a6/04a668fbcb24117321269007eb91df597be94b6accc1eab2b55dfdbd9741f333
data/06/f0/06f0d86c4008ccc7e4265cf3a41a0581ad58633f49995985a06cf576b8f9bd0d
data/0d/f3/0df36c4b119a1a8fc5eb35ab509561e341d0b4f51ed1a630547a99e90f1a2af1
Two options are worth knowing about here:
--closure hellocarries only hello's runtime content, sobinanddata.--full-closurealso carries hello's runtime dependencies, for a target that has none of the distribution installed. Do note that this pulls in everything thathellolinks against dynamically, includingglibc/lib.
Install the bundle
The bundle is signed with your build key, which this system hasn't seen before. Installing it without trusting that key is refused:
$ pkg bundle install --dry-run hello-2.12.3-1.pkgtar
error: offline: hello/bin 2.12.3-1 is signed by 1477cf8e47e28381, which signs nothing installed here;
pass --trust-key 1477cf8e47e283811c49692d0b7ab047d2ac053019eab10527a08143c1ba4998 to accept it
So install it as root, and pass the public key from Create a build key:
$ sudo pkg bundle install \
--trust-key 1477cf8e47e283811c49692d0b7ab047d2ac053019eab10527a08143c1ba4998 \
hello-2.12.3-1.pkgtar
Plan:
install:
+ hello 2.12.3-1 [/bin, /data]
fetch: 2 package(s), 37.3 KiB to download
Transaction complete: 2 installed, 0 removed (#35, see `pkg history info 35`)
The bundle carries all 52 subpackages, but by default only the runtime content gets installed, which is hello/bin and hello/data. To install specific extras, use --spec, for example --spec hello/man --spec hello/lang/de. To install everything that's in the bundle, use --full-closure.
Verify the installation
First of all, the program works:
$ which hello
/usr/bin/hello
$ hello
Hello, world!
$ hello --greeting=Bonjour
Bonjour
$ hello --version
hello (GNU Hello) 2.12.3
Copyright (C) 2026 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <https://gnu.org/licenses/gpl.html>.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Written by Karl Berry, Sami Kerola, Jim Meyering,
and Reuben Thomas.
The package manager knows about it:
$ pkg list | grep hello
hello 2.12.3-1 - [/bin, /data]
$ pkg query hello
hello/bin 2.12.3-1 x86_64
hello/data 2.12.3-1 noarch
The files on disk match their signed payloads:
$ pkg verify hello
hello/bin
ok f /usr/bin/hello
hello/data
ok d /usr/share/pkg-licenses/hello
ok f /usr/share/pkg-licenses/hello/COPYING
And the install is a recorded transaction, just like any other:
$ pkg history info 35
#35 bundle-install 2026-10-08T20:11:04Z
command: pkg bundle install --trust-key 1477cf8e...4998 hello-2.12.3-1.pkgtar
user: root (sudo from <user>)
state: 9cf216310681 -> f93ee61c3ef5
changes:
+ hello/bin 2.12.3-1 (71b3578ae934)
+ hello/data 2.12.3-1 (7cd658813bff)
What you end up with
| Artifact | What it is |
|---|---|
hello/recipe.toml | The recipe |
~/.config/pkg/build.key | Your private build and signing key. Keep it safe |
out/ | Signed local repository with 52 hello subpackages |
out/metadata/1.root.json | The repository's trust anchor, for consumers of it |
hello-2.12.3-1.pkgtar | A 340 KB offline bundle, ready to install anywhere |
/usr/bin/hello | Installed, working, verified, as transaction #35 |
Undoing it
$ sudo pkg remove hello
For more information about the pkg tool and all the extra options available for the sub commands used here, visit the Manual pages section.