Setting Up a Gentoo Binary Package Host
August 10, 2026
Compiling every package from source on every Gentoo server is a waste of time when your servers share the same architecture and USE flags. A binary package host (binhost) compiles packages once and serves the resulting binaries to client machines. A package that takes 30 minutes to compile on a client takes seconds to download and install from the binhost. This post covers configuring Portage for binary package creation, setting up the build pipeline, serving packages over nginx, mirroring the portage tree via rsync, and configuring clients to consume the packages.
How Gentoo Binary Packages Work
When Portage builds a package with the buildpkg feature enabled, it creates a binary archive alongside the normal installation. These archives contain the compiled binaries, libraries, and metadata — everything needed to install the package without recompiling.
The binary package format is gpkg (Gentoo PacKaGe), which replaced the older tbz2 format. gpkg supports multi-instance storage (multiple USE flag variants of the same package version) and optional GPG signing.
Configuring the Build Server
Portage Configuration
Create a binhost-specific configuration file at /etc/portage/make.conf.binhost:
# Binary package format and storage
BINPKG_FORMAT="gpkg"
PKGDIR="/var/cache/binpkgs"
# Build parallelism
MAKEOPTS="-j2"
# Binhost features:
# - buildpkg: Create binary packages after successful builds
# - binpkg-multi-instance: Store multiple USE flag variants of same package
# - getbinpkg: Use binary packages when available
FEATURES="buildpkg binpkg-multi-instance getbinpkg"
Source this file from your main /etc/portage/make.conf:
echo 'source /etc/portage/make.conf.binhost' >> /etc/portage/make.conf
Key Settings
BINPKG_FORMAT="gpkg" — the modern binary package format. Supports multiple USE flag variants stored simultaneously and optional GPG signing. Use this instead of the legacy tbz2 format.
PKGDIR="/var/cache/binpkgs" — where compiled packages are stored. This directory will be served over HTTP.
FEATURES="buildpkg" — tells Portage to create a binary package after every successful build. Combined with binpkg-multi-instance, this stores packages keyed by both version and USE flag combination.
MAKEOPTS="-j2" — adjust based on your build server's CPU cores. A dedicated build server can use -j$(nproc) for maximum parallelism.
Create the binary package directory:
mkdir -p /var/cache/binpkgs
Build Configurations
If your servers have different USE flag requirements (e.g., a mail server needs different flags than a web server), organize build configurations into separate directories:
mkdir -p /etc/portage/package.use.builds
For each server profile, create a directory with its package.use files and a package list:
mkdir -p /etc/portage/package.use.builds/server-mail/package.use
mkdir -p /etc/portage/package.use.builds/server-web/package.use
Create a package list for each profile. For example, /etc/portage/package.use.builds/server-mail/packages.list:
# Mail server packages
mail-mta/postfix
net-mail/dovecot
mail-filter/rspamd
dev-db/redis
dev-db/mysql
And /etc/portage/package.use.builds/server-web/packages.list:
# Web server packages
www-servers/nginx
www-servers/apache
dev-lang/php
dev-db/mysql
Copy the relevant package.use files for each profile into their directories. These override the build server's own USE flags when building for that specific profile.
The Build Script
Create /usr/local/bin/binhost-build:
#!/bin/bash
# Build packages for a specific build configuration
# Usage: binhost-build <config-name>
set -e
CONFIG_NAME="${1:-}"
BUILDS_DIR="/etc/portage/package.use.builds"
PORTAGE_DIR="/etc/portage"
if [[ -z "$CONFIG_NAME" ]]; then
echo "Usage: $0 <config-name>"
echo "Available configs:"
ls -1 "$BUILDS_DIR" 2>/dev/null || echo " (none)"
exit 1
fi
CONFIG_DIR="$BUILDS_DIR/$CONFIG_NAME"
PACKAGE_LIST="$CONFIG_DIR/packages.list"
if [[ ! -d "$CONFIG_DIR" ]]; then
echo "Error: Config directory not found: $CONFIG_DIR"
exit 1
fi
if [[ ! -f "$PACKAGE_LIST" ]]; then
echo "Error: Package list not found: $PACKAGE_LIST"
exit 1
fi
echo "=== Building packages for config: $CONFIG_NAME ==="
# Switch package.use to config-specific directory
switch_portage_dir() {
local dir_name="$1"
local target_dir="$PORTAGE_DIR/$dir_name"
local config_subdir="$CONFIG_DIR/$dir_name"
if [[ -d "$config_subdir" ]]; then
if [[ -d "$target_dir" && ! -L "$target_dir" ]]; then
mv "$target_dir" "${target_dir}.backup.$(date +%Y%m%d%H%M%S)"
fi
rm -f "$target_dir"
ln -sf "$config_subdir" "$target_dir"
fi
}
switch_portage_dir "package.use"
switch_portage_dir "package.accept_keywords"
switch_portage_dir "package.mask"
# Read package list (skip comments and empty lines)
PACKAGES=$(grep -v '^#' "$PACKAGE_LIST" | grep -v '^$' | tr '\n' ' ')
echo "Building packages: $PACKAGES"
emerge -1DuN --with-bdeps=y --keep-going --buildpkg --verbose $PACKAGES || {
echo "Warning: Some packages failed to build"
}
echo "=== Build complete for config: $CONFIG_NAME ==="
chmod 755 /usr/local/bin/binhost-build
The script temporarily symlinks the config-specific package.use directory into Portage's configuration, then builds all packages in the list. The -1 (oneshot) flag prevents packages from being added to the world file, since the build server tracks packages through its own lists.
The Sync Script
Create /usr/local/bin/binhost-sync to automate the daily sync-and-build cycle:
#!/bin/bash
# Daily sync and rebuild script for Gentoo binhost
set -e
LOGFILE="/var/log/binhost-sync.log"
LOCKFILE="/var/run/binhost-sync.lock"
BUILDS_DIR="/etc/portage/package.use.builds"
exec > >(tee -a "$LOGFILE") 2>&1
echo ""
echo "========================================"
echo "Binhost sync started: $(date)"
echo "========================================"
# Prevent concurrent runs
if [[ -f "$LOCKFILE" ]]; then
PID=$(cat "$LOCKFILE")
if kill -0 "$PID" 2>/dev/null; then
echo "Error: Another sync is already running (PID: $PID)"
exit 1
fi
rm -f "$LOCKFILE"
fi
echo $$ > "$LOCKFILE"
trap "rm -f $LOCKFILE" EXIT
# Step 1: Sync portage tree
echo "=== Step 1: Syncing portage tree ==="
rsync -rlptD --delete-after \
--exclude=/distfiles \
--exclude=/packages \
--exclude=/local \
rsync://rsync.uk.gentoo.org/gentoo-portage/ /var/db/repos/gentoo/
echo "Portage sync complete"
# Step 2: Update eix database
echo "=== Step 2: Updating eix database ==="
eix-update || echo "Warning: eix-update failed"
# Step 3: Build packages for each config
echo "=== Step 3: Building packages ==="
for config_dir in "$BUILDS_DIR"/*/; do
if [[ -d "$config_dir" ]]; then
config_name=$(basename "$config_dir")
echo "--- Building config: $config_name ---"
/usr/local/bin/binhost-build "$config_name" || {
echo "Warning: Build failed for config: $config_name"
}
fi
done
# Step 4: Clean old packages
echo "=== Step 4: Cleaning old packages ==="
eclean-pkg -n 5 || echo "Warning: eclean-pkg failed"
# Step 5: Regenerate package index
echo "=== Step 5: Regenerating package index ==="
emaint binhost --fix || echo "Warning: emaint binhost failed"
echo "========================================"
echo "Binhost sync completed: $(date)"
echo "========================================"
chmod 755 /usr/local/bin/binhost-sync
The script uses a lock file to prevent concurrent runs, syncs the portage tree from an upstream mirror, builds all configured profiles, cleans old package versions (keeping the 5 most recent), and regenerates the package index.
Scheduling with Cron
crontab -e
Add:
0 7 * * * /usr/local/bin/binhost-sync
This runs daily at 7:00 AM UTC. Adjust based on your maintenance window.
Serving Packages over nginx
Install and configure nginx to serve the binary package directory:
emerge -av www-servers/nginx
Create /etc/nginx/sites-enabled/binhost.conf:
server {
listen 80;
server_name binhost.example.com;
root /var/cache/binpkgs;
autoindex on;
location / {
try_files $uri $uri/ =404;
}
}
The autoindex on directive allows clients to browse the package directory. This is necessary for Portage to discover available packages.
rc-update add nginx default
rc-service nginx start
Serving the Portage Tree over rsync
Clients need the portage tree to resolve dependencies. Instead of each client syncing from a public mirror, serve it from the binhost via rsync.
Install rsync if not already present:
emerge -av net-misc/rsync
Create /etc/rsyncd.conf:
# Global settings
port = 873
max connections = 10
timeout = 300
use chroot = yes
read only = yes
log file = /var/log/rsyncd.log
# Portage tree mirror
[gentoo-portage]
path = /var/db/repos/gentoo
comment = Gentoo Portage Tree Mirror
exclude = /distfiles /packages /local
Enable and start the rsync daemon:
rc-update add rsyncd default
rc-service rsyncd start
Verify it's listening:
ss -lnt sport = :873
Configuring Client Machines
On each Gentoo client that should consume binary packages, add the following to /etc/portage/make.conf:
# Binary package host
PORTAGE_BINHOST="http://binhost.example.com"
# Use binary packages when available, fall back to source
FEATURES="getbinpkg"
Configure the client to sync the portage tree from the binhost instead of a public mirror. Edit /etc/portage/repos.conf/gentoo.conf:
[gentoo]
location = /var/db/repos/gentoo
sync-type = rsync
sync-uri = rsync://binhost.example.com/gentoo-portage
auto-sync = yes
Test the binary package installation:
emerge -avG dev-vcs/git
The -G (getbinpkg) flag tells Portage to prefer binary packages. If a matching binary exists on the binhost, it downloads and installs in seconds instead of compiling.
Verifying the Setup
On the Build Server
# Check that packages exist
ls /var/cache/binpkgs/
# Check nginx is serving packages
curl -s http://binhost.example.com/ | head -20
# Check rsync is serving the portage tree
rsync rsync://localhost/gentoo-portage/ | head -10
On a Client
# Verify the binhost is reachable
emerge --info | grep PORTAGE_BINHOST
# Test binary installation
emerge -avG app-editors/nano
# Should show "binary" next to the package
Summary
After completing these steps:
- A build server compiles packages once for all client machines
- Binary packages are stored in
gpkgformat with multi-instance support - nginx serves packages over HTTP for Portage clients
- rsync serves the portage tree for client synchronization
- A daily cron job keeps the portage tree and packages current
- Client machines install pre-compiled binaries in seconds instead of compiling from source
This is particularly valuable for servers running identical configurations — a fleet of web servers or mail servers can all pull the same binary packages without duplicating compilation work.