Deep Dive: The Gentoo-Nix Hybrid Architecture

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 ripgrep requires recompiling it (30 seconds of my life)
  • Upgrading neovim pulls in 47 dependencies (10 minutes)
  • Installing go means 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:

  1. Reads your config
  2. Downloads/builds necessary packages
  3. Creates symlinks in ~/.nix-profile
  4. 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 TypeNeeds nixGL?
CLI toolsNo
Terminal emulatorsYes
GUI applicationsYes
Development serversNo
Language runtimesNo

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:

  1. Creates a Btrfs snapshot
  2. Syncs with the binhost
  3. Installs binary packages
  4. Can be rolled back via snapper

User Tool Updates

# Update Nix channels
nix-channel --update

# Rebuild user environment
home-manager switch

This:

  1. Fetches latest package definitions
  2. Downloads updated binaries
  3. Atomically switches to new generation
  4. 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:

TaskPure GentooHybrid
Update ripgrep30 sec compileInstant (binary)
Update neovim10 min compile5 sec download
Update Go5 min bootstrap10 sec download
Update kernel15 min compile2 min (binhost)
Update KDE30 min compile10 min (binhost)

Rollback capability:

LayerGentooHybrid
SystemBtrfs snapshotsBtrfs snapshots
User toolsNone (manual downgrade)Home Manager generations

Reproducibility:

LayerGentooHybrid
Systemmake.conf + world fileSame
User toolsNonehome.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.