Deep Dive: The Gentoo-Nix Hybrid Architecture
The ultimate Linux system? Optimized core (Gentoo) + Reproducible userland (Nix). Here’s how to run them together.
The Problem
I love Gentoo for the system core. Kernel, drivers, desktop environment—these benefit from -march=native optimization. Compiling them for my specific CPU makes a measurable difference.
I hate Gentoo for userland CLI tools.
The pain:
- Upgrading
ripgreprequires recompiling it (30 seconds of my life) - Upgrading
neovimpulls in 47 dependencies (10 minutes) - Installing
gomeans waiting for the bootstrap (5+ minutes) - Half my updates are recompiling tools that work identically on any CPU
These tools don’t benefit from source compilation. A prebuilt ripgrep binary runs the same whether it was compiled on my machine or in a datacenter.
The realization: I should compile system software. I should not compile userland tools.
The Philosophy
Gentoo’s strength: Hardware-specific optimization. Kernel, NVIDIA drivers, KDE Plasma—these interact directly with hardware. Compilation flags matter.
Nix’s strength: Reproducibility and convenience. CLI tools, development environments, language runtimes—these don’t care about my CPU. I just want them to work.
The hybrid:
- System layer (root): Gentoo + Portage + binhost
- User layer (~/.nix-profile): Nix + Home Manager
Two package managers. Two philosophies. One system.
Installation: Nix on Gentoo
Nix can run on any Linux distribution. It installs to /nix/store and doesn’t touch the system package manager.
Single-User Mode
Multi-user mode requires systemd or complex OpenRC setup with build users. Single-user is simpler and works fine for a desktop.
# Install Nix (single-user, no daemon)
sh <(curl -L https://nixos.org/nix/install) --no-daemon
This creates:
/nix/store— The immutable package store~/.nix-profile— Links to your installed packages~/.nix-defexpr— Channel definitions
Shell Integration
Add to ~/.bashrc or ~/.zshrc:
# Nix
if [ -e ~/.nix-profile/etc/profile.d/nix.sh ]; then
. ~/.nix-profile/etc/profile.d/nix.sh
fi
Verify it works:
source ~/.bashrc
nix-env --version
Home Manager: Declarative User Environment
Raw nix-env commands are imperative. You install things, forget what you installed, and your environment drifts.
Home Manager is declarative. You write a config file describing your tools, and Home Manager makes it so.
Installing Home Manager
# Add the Home Manager channel
nix-channel --add https://github.com/nix-community/home-manager/archive/master.tar.gz home-manager
nix-channel --update
# Install Home Manager
nix-shell '<home-manager>' -A install
The Configuration File
Create ~/.config/home-manager/home.nix:
{ config, pkgs, ... }:
{
# User info
home.username = "commander";
home.homeDirectory = "/home/commander";
# State version (don't change after initial setup)
home.stateVersion = "23.11";
# Let Home Manager manage itself
programs.home-manager.enable = true;
# Packages to install
home.packages = with pkgs; [
# Modern CLI tools
ripgrep # Better grep
fd # Better find
bat # Better cat
eza # Better ls
fzf # Fuzzy finder
jq # JSON processor
yq # YAML processor
htop # Process viewer
ncdu # Disk usage
# Development
git
gh # GitHub CLI
lazygit # Git TUI
# Languages (runtimes only)
go
rustup
nodejs_20
python3
# Editors
neovim
# Shell
starship # Prompt
zoxide # Smart cd
direnv # Directory environments
];
# Program-specific configuration
programs.starship = {
enable = true;
settings = {
add_newline = false;
character = {
success_symbol = "[➜](bold green)";
error_symbol = "[➜](bold red)";
};
};
};
programs.zoxide = {
enable = true;
enableBashIntegration = true;
};
programs.direnv = {
enable = true;
nix-direnv.enable = true;
};
}
Applying Changes
home-manager switch
That’s it. Home Manager:
- Reads your config
- Downloads/builds necessary packages
- Creates symlinks in
~/.nix-profile - Updates your shell configuration
The Power: Declarative Rollback
Every home-manager switch creates a new “generation.” You can list them:
home-manager generations
2026-01-30 14:23 : id 42 -> /nix/store/abc123-home-manager-generation
2026-01-29 09:15 : id 41 -> /nix/store/def456-home-manager-generation
2026-01-28 16:42 : id 40 -> /nix/store/ghi789-home-manager-generation
Roll back to a previous generation:
home-manager switch --rollback
Or switch to a specific one:
/nix/store/def456-home-manager-generation/activate
Your entire user environment is versioned. Break something? Roll back.
The OpenGL Conflict
Here’s where things get tricky.
Nix packages are built against specific library paths in /nix/store. On NixOS, everything is in /nix/store, so it all matches.
On Gentoo, the OpenGL drivers are in /usr/lib. Nix applications can’t find them.
The symptom: GUI applications from Nix crash or show garbled graphics.
# Install Alacritty from Nix
nix-env -iA nixpkgs.alacritty
# Run it
alacritty
# ERROR: Unable to find suitable OpenGL driver
The Fix: nixGL
nixGL is a wrapper that injects the host system’s OpenGL drivers into the Nix environment.
# Install nixGL
nix-env -iA nixpkgs.nixgl.auto.nixGLDefault
Now wrap your GUI apps:
nixGL alacritty
Automating with aliases:
# ~/.bashrc
alias alacritty="nixGL alacritty"
alias kitty="nixGL kitty"
Or in Home Manager:
# In home.nix
home.shellAliases = {
alacritty = "nixGL alacritty";
kitty = "nixGL kitty";
};
Which Apps Need nixGL?
| App Type | Needs nixGL? |
|---|---|
| CLI tools | No |
| Terminal emulators | Yes |
| GUI applications | Yes |
| Development servers | No |
| Language runtimes | No |
Rule of thumb: If it opens a window, wrap it with nixGL.
The Layered Architecture
Here’s how the two package managers coexist:
┌─────────────────────────────────────────────────────────────────┐
│ USER LAYER │
│ (~/.nix-profile/bin) │
│ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ Home Manager │ │
│ │ │ │
│ │ ripgrep, fd, bat, neovim, go, rustup, nodejs │ │
│ │ (Prebuilt binaries, reproducible, easy rollback) │ │
│ └──────────────────────────────────────────────────────────┘ │
│ │
├─────────────────────────────────────────────────────────────────┤
│ SYSTEM LAYER │
│ (/usr/bin, /usr/lib) │
│ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ Portage + Binhost │ │
│ │ │ │
│ │ kernel, nvidia-drivers, kde-plasma, firefox, pipewire │ │
│ │ (Compiled with -march=native, hardware-optimized) │ │
│ └──────────────────────────────────────────────────────────┘ │
│ │
├─────────────────────────────────────────────────────────────────┤
│ HARDWARE LAYER │
│ │
│ Intel i7-4790K │ 32GB DDR3 │ RTX 4070 Ti │ NVMe │
└─────────────────────────────────────────────────────────────────┘
PATH priority: ~/.nix-profile/bin comes before /usr/bin. Nix tools take precedence.
The Update Workflow
System Updates (Kernel, KDE, Drivers)
# Using apkg (my Portage wrapper)
sudo apkg update
This:
- Creates a Btrfs snapshot
- Syncs with the binhost
- Installs binary packages
- Can be rolled back via snapper
User Tool Updates
# Update Nix channels
nix-channel --update
# Rebuild user environment
home-manager switch
This:
- Fetches latest package definitions
- Downloads updated binaries
- Atomically switches to new generation
- Can be rolled back via
home-manager rollback
The Independence
These updates are independent. I can:
- Update user tools without touching the system
- Update the system without touching user tools
- Roll back either layer separately
If a Neovim update breaks my workflow, I roll back Home Manager. The system is untouched.
If a kernel update breaks boot, I rollback via Btrfs. My user tools are untouched.
Practical Examples
Development Environment Per-Project
Using direnv and Nix flakes, I can have per-project dependencies:
# In a project directory, create flake.nix
{
description = "My project";
inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-23.11";
outputs = { self, nixpkgs }: {
devShells.x86_64-linux.default = nixpkgs.legacyPackages.x86_64-linux.mkShell {
packages = with nixpkgs.legacyPackages.x86_64-linux; [
python311
poetry
postgresql
];
};
};
}
# .envrc
use flake
When I cd into this directory, direnv automatically activates the environment. Python 3.11, Poetry, and PostgreSQL appear in my PATH. When I leave, they disappear.
No global installation. No version conflicts. Per-project isolation.
Reproducible Dotfiles
My entire home.nix is in a git repository. On a new machine:
# Clone dotfiles
git clone [email protected]:commander/dotfiles.git ~/.dotfiles
# Install Home Manager
nix-shell '<home-manager>' -A install
# Link config
ln -s ~/.dotfiles/home.nix ~/.config/home-manager/home.nix
# Apply
home-manager switch
In one command, I have my entire development environment: tools, configurations, shell settings. Identical on every machine.
What Goes Where?
The decision tree:
Does it interact with hardware?
├─ Yes → Gentoo (Portage)
│ Examples: kernel, nvidia-drivers, mesa, pipewire
│
└─ No → Does it need GUI/OpenGL?
├─ Yes → Gentoo (easier than nixGL)
│ Examples: firefox, kde-plasma, steam
│
└─ No → Nix (Home Manager)
Examples: ripgrep, neovim, go, git
Gentoo handles:
- Kernel and modules
- Graphics drivers (NVIDIA, AMD)
- Desktop environment (KDE Plasma)
- Browsers (Firefox—OpenGL for WebGL)
- Audio (Pipewire, ALSA)
- System services
Nix handles:
- CLI tools (ripgrep, fd, bat, jq)
- Development languages (Go, Rust, Node, Python)
- Editors (Neovim, Helix)
- Shell configuration (Starship, zoxide)
- Per-project environments
The Gotchas
1. Don’t Install the Same Package Twice
If both Portage and Nix install git, you’ll have two versions. PATH determines which runs. This gets confusing.
Solution: Pick one manager per package. I use Nix for git because Home Manager can also configure ~/.gitconfig.
2. Nix Shell Inherits System Environment
When you run nix-shell, it inherits your current environment. If system packages leak in, you might get unexpected behavior.
Solution: Use nix-shell --pure for isolation:
nix-shell --pure -p python311
3. Binary Cache vs Local Builds
Nix prefers downloading prebuilt binaries from cache.nixos.org. But some packages aren’t cached, or your config options differ.
Symptom: A simple package takes forever to install.
Solution: Check if it’s building:
nix-env -iA nixpkgs.somePackage --dry-run
If it says “will be built,” consider whether you really need it.
4. Nix Store Grows Forever
Every generation, every derivation stays in /nix/store until garbage collected.
# See store size
du -sh /nix/store
# Garbage collect (keeps current generation)
nix-collect-garbage
# Also delete old generations
nix-collect-garbage -d
I run nix-collect-garbage -d weekly.
The Result
Update time comparison:
| Task | Pure Gentoo | Hybrid |
|---|---|---|
| Update ripgrep | 30 sec compile | Instant (binary) |
| Update neovim | 10 min compile | 5 sec download |
| Update Go | 5 min bootstrap | 10 sec download |
| Update kernel | 15 min compile | 2 min (binhost) |
| Update KDE | 30 min compile | 10 min (binhost) |
Rollback capability:
| Layer | Gentoo | Hybrid |
|---|---|---|
| System | Btrfs snapshots | Btrfs snapshots |
| User tools | None (manual downgrade) | Home Manager generations |
Reproducibility:
| Layer | Gentoo | Hybrid |
|---|---|---|
| System | make.conf + world file | Same |
| User tools | None | home.nix in git |
The Philosophy
Use the right tool for each layer.
Gentoo is exceptional at what it does: building a system tailored to your hardware. The kernel I run is optimized for my exact CPU. The NVIDIA driver is compiled for my exact kernel. KDE Plasma uses my GPU efficiently.
Nix is exceptional at what it does: reproducible user environments. My CLI tools are identical on every machine. My development environments are isolated. My configurations are version-controlled.
Neither tool alone gives me everything I want. Together, they give me the best Linux desktop I’ve ever used.
Related: Argo OS Journey Part 4: The Hybrid Vision, The apkg Package Manager.