Maintaining a fleet of Gentoo systems, or struggling to compile packages on an underpowered server, can be an incredibly tedious process. Waiting hours for a single package to compile can quickly add up, and eventually you may try to burn down your home lab.
Unless you rich, your home servers are likely stuff of 10 years ago made of mismatch stuff lying around. A partial solution is simple do not touching the portage flags at all and use the Gentoo provided binhost. But this presents two issues: not everything available on portage exist as binary package at their binhost mirrors, and the second is that you give away customization of the packages that you do use.
The solution that doesn’t involve dumping your beloved Gentoo? A Binhost. To put it short: get that beef main PC of yours, that workhorse, and makes it do the work of compiling stuff for your servers.
Prerequisites
- A machine running Gentoo (this will be the host machine running the build), with root access.
- Your host and servers CPU are of the same architecture.
- Access to all Gentoo clients that will receive binary packages (to copy the
worldfile and/etc/portage/configurations from). - Placeholders: Throughout this guide, you will see
<host>and<keyid>.<host>: Replace this with the hostname of the machine you intend to serve these binaries to.<keyid>: Replace this with the unique ID of your newly generated GPG key.
Step 1: Generating and Exporting GPG Keys
Portage requires binary packages to be signed by a GPG key to ensure their integrity and authenticity. Yes, you can disable that, but it’s a bad idea to do so. Before we start building our environment, we need to generate a key pair on our main host.
Run the following command to launch the key generation wizard:
gpg --full-generate-key
When prompted by the wizard, follow these specific steps:
- Select 1 (RSA and RSA): This algorithm provides a secure and widely compatible standard for package signing.
- Select 0 (key does not expire): Since this key will be used to sign packages for years to come, setting it to never expire prevents the headaches of Portage suddenly refusing to validate packages when a key expires.
- Name and Email: Fill these in with the name of your binhost and a contact email address.
- Passphrase: Leave this blank. Because this binhost is intended for automated builds, having a passphrase would require manual intervention every time Portage attempts to sign a package.
Once the key is generated, we need to find its unique ID. Run:
gpg --list-keys --fingerprint
Look for your newly created key in the output and copy the ID (this will be your <keyid>).
Next, export both the public and private keys. We will need to move these keys inside our chroot environment later:
gpg --armor --export <keyid> > binhost-public.asc
gpg --armor --export-secret-keys <keyid> > binhost-private.asc
Step 2: Preparing the Binhost Root Directory
Now we will set up a dedicated directory structure that will serve as the filesystem root for our chroot environment.
mkdir ~/binhost/<host>
cd binhost/<host>
(Note: If you are building binaries for multiple machines with the exact same architecture and CFLAGS, you can use a generic name here. If they differ, you should create separate directories for each).
Download the latest Stage 3 tarball. This provides the bare minimum files required to bootstrap a Gentoo system. The link below where the latest by the time this where wrote. Be aware however: it should match the stage 3 that used on your servers, the link below is of the minimal one using OpenRC.
wget https://distfiles-cdn-origin.gentoo.org/releases/amd64/autobuilds/current-stage3-amd64-openrc/stage3-amd64-openrc-20260614T170130Z.tar.xz
Extract the tarball into the current directory. The --numeric-owner flag is crucial to ensure file permissions match the exact numeric IDs expected by Gentoo:
tar xpvf stage3-*.tar.xz --xattrs-include='*.*' --numeric-owner
To ensure the binhost builds exactly the same packages (and with the same USE flags) as your target machine, copy the world file and Portage configuration from your servers.
(Make sure to replace user@server.domain with the actual SSH credentials and hostname of your reference machine, and pay attention that the destination folder doesn’t have the leadind /!! You are inside binhost/ folder).
scp -r user@server.domain:/var/lib/portage/world var/lib/portage/world
scp -r user@server.domain:/etc/portage/ etc/
Step 3: Syncing and Preparing the Chroot Environment
Before we enter the chroot, we need to sync the Portage tree and prepare the necessary filesystem mounts.
Sync the Portage tree on the host machine:
emerge --sync
Copy the freshly synced repository into our chroot directory:
cp -a /var/db/repos/gentoo/ var/db/repos/
Move the GPG keys we exported earlier into the chroot’s root home directory so they are accessible inside the environment:
mv ~/*.asc ./home/root
Mount the necessary filesystems to give the chroot environment access to system resources:
mount --types proc /proc proc
mount --rbind /dev dev
To allow the chrooted environment to access the internet (which is necessary for emerge to fetch source code), copy the DNS resolver configuration:
cp --dereference /etc/resolv.conf etc
Step 4: Entering the Chroot and Configuring GPG
It’s time to enter our newly created environment. Run:
chroot . /bin/bash
Update your shell prompt to visually remind you that you are now inside the chroot:
export PS1="(chroot) $PS1"
Inside the chroot, import the private GPG key:
gpg --import "binhost-private.asc"
Portage relies on the trust level of the GPG key. We must set the trust to “ultimate” so that Portage automatically trusts the packages signed by this key without manual confirmation.
Run the key editor:
gpg --edit-key <keyid>
When the GPG prompt appears, type trust to change the trust level. You will be presented with a menu. Select the highest option (usually 5) to set the trust level to Ultimate. Exit the prompt by typing quit.
Verify that the trust database has updated correctly:
gpg --check-trustdb
Finally, we need to set up Portage’s specific GPG homedir with the public key so that the package manager can validate signatures. This one will configure the GPG of portage, hence that homedir argument, after all, it’s the portage that will use the public key to verify the signature made by your private key installed on your root user GPG.
gpg --homedir=/etc/portage/gnupg --import "binhost-public.asc"
gpg --homedir=/etc/portage/gnupg --edit-key <keyid>
Repeat the process of changing the trust level to Ultimate and then quit. Verify Portage’s trust database:
gpg --homedir=/etc/portage/gnupg --check-trustdb
Step 5: Tweak the portage make.conf to generate binpkgs
Now, it’s time to change the make.conf to compile your binary packages as it goes compiling everything else. Below is a exemple of what it should look like. Some important points to take note of:
- BINPKG_GPG_SIGNING_KEY must have your GPG key id.
- The -march must be the minimal common denominator between all servers and the host. Remember, the host needs to execute compilers on a chroot environment that mimics your server, and your server must be able to run the binaries. A safe strategy is setting -march to core2 (the minimal one that has all instruction from Intel and AMD of the past 15 years) and -mtune set to your server CPU. That assures the your host will still be able to execute all compilers needed to create binpkgs for your server, and that your server will receive binaris with some optimization, but not deep enough to introduce incompatibilities with the set -march.
- Set MAKEOPTS to match the number of cores and threads of your host CPU.
COMMON_FLAGS="-march=core2 -O2 -pipe -mtune=znver1"
RUSTFLAGS="${RUSTFLAGS} -C -C opt-level=2 --target-cpu=core2 --tune-cpu=znver1"
CFLAGS="${COMMON_FLAGS}"
CXXFLAGS="${COMMON_FLAGS}"
FCFLAGS="${COMMON_FLAGS}"
FFLAGS="${COMMON_FLAGS}"
MAKEOPTS="-j16 -l17"
# NOTE: This stage was built with the bindist USE flag enabled
# This sets the language of build output to English.
# Please keep this setting intact when reporting bugs.
LC_MESSAGES=C.UTF-8
BINPKG_GPG_SIGNING_GPG_HOME="/root/.gnupg"
BINPKG_GPG_SIGNING_KEY="<keyid>"
FEATURES="${FEATURES} buildpkg binpkg-signing"
# license fuckery
ACCEPT_LICENSE="-* @FREE @BINARY-REDISTRIBUTABLE"
# dist kernel galore
USE="dist-kernel"
GENTOO_MIRRORS="rsync://gentoo.c3sl.ufpr.br/gentoo/"
GRUB_PLATFORMS="efi-64"
# gpu video hardware acceleration
USE="vaapi"
Step 6: Building the Base System
Before we can build anything, we need to create a directory required by modern Portage and system utilities:
mkdir /run/lock
Now for the main event. We are going to run emerge --emptytree:
emerge --emptytree
What does this do?
The --emptytree flag tells Portage to assume the system has absolutely no packages installed and to proceed to install everything listed in the world file we copied earlier earlier. Because your /etc/portage/make.conf inside this environment is configured to act as a binhost, Portage will compile every single package and store the resulting .tbz2 binary files in the binhost repository directory (usually /usr/portage/packages/).
This process will take a significant amount of time depending on the number of packages in your world file. You can sit back and let it run. Once it finishes, you will have a complete repository of pre-compiled binaries tailored perfectly to your target machine!
From now on, you can just use the most common update line when creating binary packages for your servers, and it will be much faster:
emerge --ask --update --deep --newuse @world
Conclusion
Congratulations! You have successfully set up a Gentoo binhost chroot environment.
To wrap up your setup, you will eventually need to configure a lightweight web server (like Nginx or Apache) to serve the contents of the /usr/portage/packages/ directory over HTTP so your other machines can reach it. Then, on your client machines (your servers or underpowered hardware), you will simply configure PORTAGE_BINHOST in their /etc/portage/make.conf and run emerge --emptytree to instantly pull your freshly compiled binaries.