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 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:

  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:

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.