How to Package Software for pkg Using poudriere on FreeBSD

Learn how to use poudriere to build custom packages for FreeBSD’s pkg package manager.

Introduction

FreeBSD’s package management system, pkg, offers a reliable way to install and manage software. While pre-built packages are available from the official FreeBSD repositories, there are many scenarios where building custom packages becomes necessary:

  • Creating packages with custom compile-time options
  • Packaging proprietary software for internal distribution
  • Testing package modifications before submitting them to the ports tree
  • Maintaining a private repository of packages optimized for specific hardware

Poudriere (French for “powder magazine”) is the official tool used by the FreeBSD project to build packages. It provides a clean, isolated build environment ensuring reproducible results and high-quality packages.

This guide walks through the complete process of setting up poudriere to build and maintain a custom package repository on FreeBSD.

Prerequisites

  • A FreeBSD system (version 12.0 or newer recommended)
  • Root access or sudo privileges
  • At least 4GB of RAM (8GB+ recommended for larger builds)
  • Sufficient disk space (20GB+ recommended)
  • Basic familiarity with FreeBSD ports system

1. Installing poudriere

The first step is to install poudriere from the FreeBSD ports collection:

# Install poudriere
pkg install poudriere

# Create necessary directories
mkdir -p /usr/local/poudriere/data /usr/local/poudriere/ports /usr/local/poudriere/cache

2. Configuring poudriere

Configuration is done through the /usr/local/etc/poudriere.conf file. While poudriere installs a sample configuration file, you should modify it to suit your environment:

# Copy the sample configuration
cp /usr/local/etc/poudriere.conf.sample /usr/local/etc/poudriere.conf

Edit the file with your preferred editor. Here are the essential settings to configure:

# Base filesystem where poudriere will store data
BASEFS=/usr/local/poudriere

# Location of the poudriere ports tree
POUDRIERE_DATA=${BASEFS}/data

# The directory where packages will be stored
PKG_REPO_SIGNING_KEY=/usr/local/etc/ssl/keys/pkg.key

# ZFS configuration (if using ZFS)
ZPOOL=zroot
ZROOTFS=/poudriere

# Use tmpfs for building (if you have sufficient RAM)
USE_TMPFS=yes

# Number of parallel build jobs (typically # of cores + 1)
PARALLEL_JOBS=5

# URL where your repository will be hosted
URL_BASE=http://pkg.example.com

If you’re using ZFS (recommended), make sure the specified pool exists. If you’re not using ZFS, comment out the ZFS-related parameters.

3. Setting Up SSL Keys for Package Signing

For security, pkg validates packages using digital signatures. To set this up:

# Create a directory for the keys
mkdir -p /usr/local/etc/ssl/keys

# Generate a key pair
openssl genrsa -out /usr/local/etc/ssl/keys/pkg.key 4096
openssl rsa -in /usr/local/etc/ssl/keys/pkg.key -pubout > /usr/local/etc/ssl/keys/pkg.cert

# Secure the private key
chmod 0400 /usr/local/etc/ssl/keys/pkg.key

4. Creating a Ports Tree

Poudriere needs a ports tree to build packages from:

# Fetch a fresh ports tree
poudriere ports -c -m git+https

This creates a ports tree named “default”. You can create multiple ports trees with different names:

# Create a quarterly ports tree
poudriere ports -c -m git+https -B quarterly -p quarterly

5. Creating Build Jails

Poudriere builds packages inside isolated jails. Create one for your target FreeBSD version:

# Create a jail for FreeBSD 13.1-RELEASE
poudriere jail -c -j 131amd64 -v 13.1-RELEASE -a amd64

The -j flag specifies a name for the jail, -v indicates the FreeBSD version, and -a specifies the architecture.

You can maintain multiple jails for different FreeBSD versions:

# Create a jail for FreeBSD 12.3-RELEASE
poudriere jail -c -j 123amd64 -v 12.3-RELEASE -a amd64

6. Configuring Port Options

Before building packages, you can customize the build options for ports:

# Configure options for nginx
poudriere options -j 131amd64 -p default www/nginx

This opens a dialog where you can select which features to enable in the built package.

7. Creating a Build List

Create a text file listing the ports you want to build:

# Create a list file
cat > /usr/local/etc/poudriere-list.txt << EOF
www/nginx
databases/postgresql13-server
lang/python3
editors/vim
EOF

8. Building Packages

Now you can build packages using the list file:

# Build packages
poudriere bulk -j 131amd64 -p default -f /usr/local/etc/poudriere-list.txt

For building a single package:

# Build just nginx
poudriere bulk -j 131amd64 -p default www/nginx

Poudriere automatically handles dependencies, building everything needed for your specified packages.

9. Setting Up a Package Repository

After building packages, configure a web server to serve them. Here’s a basic Nginx configuration:

server {
    listen 80;
    server_name pkg.example.com;
    root /usr/local/poudriere/data/packages/131amd64-default;
    
    location / {
        autoindex on;
    }
}

10. Configuring Clients to Use Your Repository

On client machines, create a repository configuration file:

mkdir -p /usr/local/etc/pkg/repos

Create a file /usr/local/etc/pkg/repos/custom.conf:

custom: {
    url: "http://pkg.example.com/131amd64-default",
    mirror_type: "http",
    signature_type: "pubkey",
    pubkey: "/usr/local/etc/ssl/keys/pkg.cert",
    enabled: yes
}

FreeBSD: { enabled: no }

This disables the official FreeBSD repository and enables your custom one. Copy the public key (pkg.cert) to client machines.

11. Advanced poudriere Techniques

Using Make.conf for Package Customization

Create custom make.conf files to set global build options:

mkdir -p /usr/local/etc/poudriere.d

Create /usr/local/etc/poudriere.d/131amd64-make.conf:

# Use clang as compiler
CC=clang
CXX=clang++

# Optimize for your CPU
CPUTYPE?=skylake

# Default package options
OPTIONS_SET=OPTIMIZED_CFLAGS THREADS INET6
OPTIONS_UNSET=DEBUG X11 EXAMPLES DOCS NLS

Setting Up Building Hooks

Poudriere supports hooks that run at different stages of the build process:

mkdir -p /usr/local/etc/poudriere.d/hooks

Create a post-build hook (/usr/local/etc/poudriere.d/hooks/post-build.sh):

#!/bin/sh
# This script runs after each package is built

logger -t poudriere "Package $1 built with status $2"

Make the script executable:

chmod +x /usr/local/etc/poudriere.d/hooks/post-build.sh

Automating Repository Updates

Set up a cron job to regularly update your ports tree and rebuild packages:

# Add to root's crontab
0 2 * * * /usr/local/bin/poudriere ports -u && /usr/local/bin/poudriere bulk -j 131amd64 -p default -f /usr/local/etc/poudriere-list.txt

12. Troubleshooting Common Issues

Build Failures

If a build fails, check the logs in /usr/local/poudriere/data/logs/bulk/. The logs are organized by jail name, ports tree, and build date.

Package Conflicts

If you encounter conflicts between packages, you may need to modify your port options or use pkg.conf to set package preferences.

Disk Space Issues

Building packages can consume a lot of disk space. Monitor space usage and consider:

  • Using ZFS with compression
  • Cleaning old package builds: poudriere pkgclean -j 131amd64 -p default
  • Removing distfiles: poudriere distclean -p default

13. Creating Custom Ports

To package custom software not in the ports tree:

  1. Create a skeleton port:
mkdir -p /usr/ports/local/myapp
  1. Create minimal port files:

    • Makefile - Defines build instructions
    • distinfo - Contains checksums of source files
    • pkg-descr - Description of the software
    • pkg-plist - List of files the package will install
  2. Add your port to the build list and build it with poudriere.

Conclusion

Poudriere is a powerful tool for creating and maintaining FreeBSD packages. With proper configuration, it provides a reliable way to build custom packages for your environment.

By following this guide, you’ve learned how to:

  • Install and configure poudriere
  • Create build jails and ports trees
  • Customize port options
  • Build packages and create a repository
  • Configure clients to use your custom packages

This setup gives you complete control over your software ecosystem, allowing you to tailor packages to your specific requirements while maintaining the robustness and security of FreeBSD’s package management system.