thisago's blog

Guix/Nix in a Fedora Minimal TemplateVM

Table of Contents

Solene has a pretty straightforward guide to setup Nix on a AppVM without daemon. I recommend reading it first: Solene'% : How to install Nix in a Qubes OS AppVM

This guide covers the installation of the Guix/Nix daemon (as root) in a minimal Fedora TemplateVM, and the setup of bind-dirs for persistence of the derivations built from AppVMs.

Below steps covers the , and even below, the Nix steps, which you can append or replace. It works together too.

Steps for Installation of Guix

  1. [dom0] Create TemplateVM and connect the NetVM to it. In my case, called fedora-42-minimal-guix
  2. [dom0] Install the passwordless and networking Qubes agent in TemplateVM via dom0.1

    qvm-run -u root fedora-42-minimal-guix -- "dnf install -y qubes-core-agent-passwordless-root qubes-core-agent-networking && systemctl restart qubes-network-uplink.service"
    
  3. [TemplateVM] Install Guix (YOLO script)
    1. Dependencies

      sudo dnf install -y wget xz
      
    2. Run Guix installation script

      cd /tmp &&
        wget -O guix-install.sh https://guix.gnu.org/install.sh &&
        wget -O x86_64-linux-guix-binary.tar.xz "https://ci.guix.gnu.org/download/3143" &&
        chmod +x guix-install.sh &&
        yes '' | sudo env GUIX_BINARY_FILE_NAME=x86_64-linux-guix-binary.tar.xz ./guix-install.sh
      

      You can grab the latest artifact with the following script (requires xq in your PATH):

      curl -sL 'https://ci.guix.gnu.org/search/latest?query=spec:tarball+status:success+system:x86_64-linux+guix-binary.tar.xz' |
          xq -q 'ul.list-group.d-flex.flex-row > a' -a href |
          xargs -I{} echo 'https://ci.guix.gnu.org{}'
      
      https://ci.guix.gnu.org/download/3143
      
    3. Update Guix official channel.
      Using Codeberg because git.guix.gnu is now down for me.

      guix pull --url=https://codeberg.org/guix/guix
      
    4. Create a profile directory for user.

      sudo mkdir -p /var/guix/profiles/per-user/user
      sudo chown user:user /var/guix/profiles/per-user/user
      
    5. Setup the AppVM base home by linking the user profile.2

      sudo mkdir -p /etc/skel/.config/guix/
      sudo ln -s /var/guix/profiles/per-user/user/current-guix /etc/skel/.config/guix/current
      
    6. Persist global Guix binary symlink on AppVMs. See documented pitfall below.

      sudo ln -s /usr/local/bin/guix /usr/local.orig/bin/guix
      
    7. Optionally remove the installed dependencies

      sudo dnf remove -y wget xz
      
  4. [dom0] Shutdown TemplateVM and remove NetVM from it.
  5. Configure bind-dirs for persistence at /gnu/store and /var/guix at AppVM. Since Guix Store is at /gnu/store and the profiles at /var/guix/profiles, we will persist both.
    1. [AppVM] Configure the directories for bind-dirs.

      sudo mkdir -p /rw/config/qubes-bind-dirs.d/
      echo "binds+=( '/gnu/store' '/var/guix' )" | sudo tee /rw/config/qubes-bind-dirs.d/50_user.conf
      
    2. Shutdown AppVM
    3. [dom0] Make sure to increase the AppVM private disk because the /gnu/store will now be persisted on it, which was previously on the TemplateVM.
      • Expect a slower startup time for the initial copy process.
      • For reference, my newly created AppVM had 960K of disk usage, now it has 2.5G.
    4. [dom0] Start AppVM back again.
    5. [AppVM] Optionally, to test the persistence, run a shell with a package, reboot and run again to expect no downloads to be made.

      guix shell hello
      

Done! Now you have a persisting AppVM with Guix.

Nix installation

Replace/append at Guix steps (referenced in parenthesis) with below steps, the other steps remains exactly the same, and required.

  1. (3) [TemplateVM] Install Nix (YOLO script)
    1. Dependencies

      sudo dnf install -y xz
      
    2. Run Nix installation script

      sh <(curl --proto '=https' --tlsv1.2 -L https://nixos.org/nix/install) --daemon --yes
      

      For reference, the current Nix hash I am installing:

      Linux.x86_64)
          hash=0737755f3106e8dffbf26a77af398888e8421974b91fe2b86052c891f71e16b5
          path=cp85f1ak1aww5gbaj474w8rgvkj0nidi/nix-2.32.4-x86_64-linux.tar.xz
          system=x86_64-linux
      
    3. Setup the AppVM base home
      • Link the profile3, 4

        sudo mkdir -p /etc/skel/.local/state/nix/
        sudo ln -s /nix/var/nix/profiles/default /etc/skel/.local/state/nix/profile
        # You might want to setup for TemplateVM user too
        mkdir -p ~/.local/state/nix/
        ln -s /nix/var/nix/profiles/default ~/.local/state/nix/profile
        
      • Source nix.sh in users' .bashrc.5

        echo "test -f /home/user/.local/state/nix/profile/etc/profile.d/nix.sh && source /home/user/.local/state/nix/profile/etc/profile.d/nix.sh" | sudo tee -a /etc/skel/.bashrc
        # You might want to setup for TemplateVM user too
        echo "test -f /home/user/.local/state/nix/profile/etc/profile.d/nix.sh && source /home/user/.local/state/nix/profile/etc/profile.d/nix.sh" | tee -a ~/.bashrc
        
    4. Optionally remove the installed dependencies

      sudo dnf remove -y xz
      
  2. (5) [AppVM] Configure bind-dirs for persistence at /nix. Nix persists all at /nix, so it's enough.

    sudo mkdir -p /rw/config/qubes-bind-dirs.d/
    echo "binds+=( '/nix' )" | sudo tee /rw/config/qubes-bind-dirs.d/50_user.conf
    # NOTE: If you're installing both together, instead append the '/nix' with the '/gnu/store' and '/var/guix'. Example:
    # echo "binds+=( '/gnu/store' '/var/guix' '/nix' )" | sudo tee /rw/config/qubes-bind-dirs.d/50_user.conf
    
  3. (5.5) [AppVM] Example package to perform the test:

    nix-shell -p hello
    

Nice! Now you have a persisting AppVM with Nix. (Or both!)

In my case, both Guix and Nix (with their hello packages) took 3.5G from my AppVM.

Pitfalls I Faced

  • Guix installation script fetches the tarball from a FTP mirror, and the latest release is from 12/2022.

    guix-binary-1.4.0.x86_64-linux.tar.xz 18-Dec-2022 21:16 102312024
    guix-binary-1.4.0.x86_64-linux.tar.xz.sig 18-Dec-2022 21:16 833
    

    And I got stuck in this already solved issue: #344 - {rootless guix-daemon} `guix build` error: read-only file system. was …

  • Guix stores the profiles (and its db) at /var/guix, so it need persistence too. And only persisting /var/guix/profiles/per-user/user won't work.
  • Nix wasn't sourcing the profile, so I had to force it to source again. I'm new at Nix so there might be a better way to solve this.
  • The qubes-mount-dirs.service has a timeout of 60s. I was unable to setup bind-dirs for Nix and Guix at same time on two tests. In my case, the Guix had worked but Nix got corrupted, because it was the last on my bind-dirs setup.

    The notifications I got:

    [user@dom0 Desktop]$ grep -B 3 -A 2 'Cannot connect to qrexec agent for 60 seconds' ~/.cache/xfce4/notifyd/log
    [2025-11-09T07:44:31.486420-05]
    app_name=org.qubes.qui.tray.Domains
    summary=Qube Status: ix-test-for-blog-again
    body=Qube ix-test-for-blog-again has failed to start: Cannot connect to qrexec agent for 60 seconds, see /var/log/xen/console/guest-ix-test-for-blog-again.log for details
    app_icon=dialog-warning
    expire-timeout=-1
    --
    [2025-11-09T07:53:16.712243-05]
    app_name=org.qubes.qui.tray.Domains
    summary=Qube Status: ix2
    body=Qube ix2 has failed to start: Cannot connect to qrexec agent for 60 seconds, see /var/log/xen/console/guest-ix2.log for details
    app_icon=dialog-warning
    expire-timeout=-1
    --
    [2025-11-09T07:58:36.926065-05]
    app_name=org.qubes.qui.tray.Domains
    summary=Qube Status: ix-test-for-blog-again
    body=Qube ix-test-for-blog-again has failed to start: Cannot connect to qrexec agent for 60 seconds, see /var/log/xen/console/guest-ix-test-for-blog-again.log for details
    app_icon=dialog-warning
    expire-timeout=-1
    

    To solve this, you can setup bind-dirs for Guix and then for Nix (no order here). Also make sure to not bloat the AppVM and TemplateVM with packages.

    If your qube get killed by dom0 in this process, the AppVM will have the directory setupped by bind-dirs corrupted, and to fix the it without creating a new AppVM, you can delete the (incomplete) copied directories /rw/bind-dirs/{gnu,nix,var}/ and decrease the copy period at startup by setupping them separately, then reboot AppVM to make it copy again.

    This is a annoying limitation that blocks from adding a prelude set of packages in TemplateVM. I still don't know how increase this timeout at dom0.

  • Guix makes it its binary available for all users by linking at /usr/local/bin, but this directory is also persisted at AppVMs.6
  • Audio not working, missing pipewire-qubes Fedora package. Ideally it should be installed via Guix.

Outro

Thanks for reading, and happy reproducibilities.

Footnotes:

2

Ref: See sys_create_store at Guix install.sh

3

Note that default is /nix/var/nix/profiles/per-user/root/profile.

4

rg '\.local/state/nix/' ./nixpkgs ./nixpkgs/nixos/modules/config/users-groups.nix: "$HOME/.local/state/nix/profile"

5

I think the /etc/profile.d/nix.sh should source it but unless I source it again (__ETC_PROFILE_NIX_SOURCED='' source /etc/profile.d/nix.sh), it doesn't works. So I preferred to source from my symlink at user home.

See the source code here.
Generated at 2025-12-03 Wed 10:01 +0000 by Emacs 29.4 (Org mode 9.6.15)