Setting up a Gentoo Binary Package Repository with a Custom Portage Configuration
After completing this guide, users will be able to build binary packages tailored to a target system, while only running the build tools on the host system.
Specifically, packages may be built with CFLAGS and USE flags tailored to the target's CPU, even if that makes them incompatible with the host's CPU.
Requirements
- A host device:
- with the same CPU architecture as the target device (e.g. amd64)
- that is powerful enough to compile packages
- with a Gentoo installation.
emerge
, along with a few build tools, from this environment will be used to build packages.- If a Gentoo installation is not available, set up a Gentoo chroot environment. Multilib and desktop support are not needed, the init system does not matter and musl is fine.
- A target device
Host setup
The binary packages can't be built directly in the host's Gentoo installation, since the host may have different settings and a different world set than the target. Thus, a separate environment needs to be set up. This environment will mimic the target's environment.
Setting up the binhost's environment
First, set up a Gentoo chroot environment inside the host's
Gentoo installation (say, at /binhost/
). The stage file should contain the
same init system, C library and level of multilib support as the target.
This environment will serve the following purposes:
- It will store the world file and Portage configuration used to build the binary packages.
- It will store the built binary package files.
- Those packages will be installed in this environment.
- No software in this environment will be run directly.
CHROOT_DIR=binhost
STAGE_ARCHIVE=https://distfiles.gentoo.org/releases/amd64/autobuilds\
/20250601T163943Z/stage3-amd64-musl-20250601T163943Z.tar.xz
mkdir -- "$CHROOT_DIR"
cd -- "$CHROOT_DIR"
wget -- "$STAGE_ARCHIVE"
tar --extract --preserve-permissions --file stage3-*.tar.xz --xattrs-include='*.*' --numeric-owner
Hosting the binary package repository
By default, binary packages are stored in /var/cache/binpkgs/
. Being a binary
package host means exposing this directory to other device(s) (the target in
this case).
The easiest way to do this without installing any additional software is using Python's built-in HTTP server:
PORT=$RANDOM
python -m http.server --directory /binhost/var/cache/binpkgs/ $PORT
The official Gentoo wiki has a list of alternative ways
to share the binpkgs
directory with the target.
If the user can access these files from http://[public IP]:[port]/, then anyone can. This should be a temporary setup to get the target bootstapped.
Once the binhost has been set up, the port should be closed to the public using a firewall, and a secure private connection should be established between the two devices using something like Tailscale VPN.
Many users will be behind NAT or CGNAT, which means that outside devices cannot establish connections with the host. In this case, the host can temporarily use a tunnel such as Tailscale funnel or Cloudflare Tunnel to expose the HTTP server to the internet.
Target setup
Portage configuration
In order for binary packages compiled for the binhost to be installable on the target, both need to have the same configuration.
Ensure that /binhost/
and the target have the same Portage
configuration (/etc/portage/
, /var/lib/portage/world
and
/var/lib/portage/world_sets
). The configuration can be sent from one
machine to the other using scp
, as long as the sshd
service is running
on the receiver and is accessible over the network.
# Copying from the host to the target while logged in on the host
scp -r /binhost/etc/portage/ root@target:/etc/
scp -r /binhost/var/lib/portage/world* root@target:/var/lib/portage/
# Copying from the host to the target while logged in on the target
scp -r root@host:/binhost/etc/portage/ /etc/
scp -r root@host:/binhost/var/lib/portage/world* /var/lib/portage/
# Copying from the target to the host while logged in on the host
scp -r root@target:/etc/portage/ /binhost/etc/
scp -r root@target:/var/lib/portage/world* /binhost/var/lib/portage/
# Copying from the target to the host while logged in on the target
scp -r /etc/portage/ root@host:/binhost/etc/
scp -r /var/lib/portage/world* root@host:/binhost/var/lib/portage/
If the target's Portage configuration is still the default, now is the time to configure it (either on the target, or on the binhost) (Handbook: Alpha AMD64 HPPA (PA-RISC) MIPS PowerPC PPC64 SPARC x86).
-march=native
optimizes for the CPU that the compiler is currently running
on, which is the host's CPU, not the target's. Packages produced this way will
most likely not run on the target.
Run the following command on the target to see what
-march=native
evaluates to:
gcc -v -E -x c -march=native -c /dev/null -o /dev/null 2>&1 | grep /cc1 | grep mtune
Adding the binhost to the target
The target will need to know where to download the binary packages from. Add
the HTTP server's URL to /etc/portage/binrepos.conf
:
[binhost]
sync-uri = http://[ip]:[port]/binhost
priority = 10
Replace [ip]
with the IP address or domain name of the host device and
[port]
with the port that the HTTP server is listening on. Replace http
with https
if the HTTP server is being accessed through a secure tunnel such
as Tailscale funnel or Cloudflare Tunnel.
See the official Gentoo wiki for more information.
Building packages
Building packages on the host (for the binhost)
To use the host's Gentoo installation to create binary packages for the binhost (which will later be copied to the target), run the following command on the host:
ROOT=/binhost/ \
PORTAGE_CONFIGROOT=/binhost/ \
PKGDIR=/binhost/var/cache/binpkgs/ \
emerge --buildpkg --root-deps "$@"
("$@"
is a placeholder for the actual list of packages, along with any
additional arguments for emerge
).
Explanation
$ROOT
specifies where the packages will be installed.$PORTAGE_CONFIGROOT
specifies where the Portage configuration will be read from. This is the binhost's configuration, which should be the same as the target's configuration in order for the binary packages to be compatible.$PKGDIR
specifies where the binary packages will be stored.--buildpkg
(-b
) tellsemerge
to build binary packages and save them in$PKGDIR
, in addition to installing them.--root-deps
ensures that build dependencies are also built for the$ROOT
system (the binhost).
Emerge will automatically build build dependencies on the host system before
attempting to use them to build packages for the binhost. --root-deps
ensures
that these build dependencies are also built for the binhost, because the
target will need them when installing the binary packages.
Installing packages on the target
Once built on the binhost, packages can be installed on the target using sudo emerge --getbinpkgonly "$@"
.
Keeping the binhost and target in sync
In order for the environments to remain consistent, emerge commands should be run on both the binhost and the target. Consider adding the following script to the host:
#!/bin/env sh
ROOT=/binhost/ \
PORTAGE_CONFIGROOT=/binhost/ \
PKGDIR=/binhost/var/cache/binpkgs/ \
emerge --buildpkg --root-deps "$@" \
&& ssh -t user@target sudo emerge --getbinpkgonly "$@"
-t
tells ssh
to allocate a pseudo-terminal, which allows sudo to ask for
the password on the host machine (where the command is run from) instead of on
the target.