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 gpkg format 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.