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
- [dom0] Create TemplateVM and connect the NetVM to it. In my case, called
fedora-42-minimal-guix [dom0] Install the passwordless and networking Qubes agent in TemplateVM via
dom0.1qvm-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"- [TemplateVM] Install Guix (YOLO script)
Dependencies
sudo dnf install -y wget xz
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
xqin yourPATH):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
Update Guix official channel.
Using Codeberg becausegit.guix.gnuis now down for me.guix pull --url=https://codeberg.org/guix/guix
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
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
Persist global Guix binary symlink on AppVMs. See documented pitfall below.
sudo ln -s /usr/local/bin/guix /usr/local.orig/bin/guix
Optionally remove the installed dependencies
sudo dnf remove -y wget xz
- [dom0] Shutdown TemplateVM and remove NetVM from it.
- Configure
bind-dirsfor persistence at/gnu/storeand/var/guixat AppVM. Since Guix Store is at/gnu/storeand the profiles at/var/guix/profiles, we will persist both.[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
- Shutdown AppVM
- [dom0] Make sure to increase the AppVM private disk because the
/gnu/storewill 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.
- [dom0] Start AppVM back again.
[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.
- (3) [TemplateVM] Install Nix (YOLO script)
Dependencies
sudo dnf install -y xz
Run Nix installation script
sh <(curl --proto '=https' --tlsv1.2 -L https://nixos.org/nix/install) --daemon --yesFor 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- Setup the AppVM base home
-
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.shin users'.bashrc.5echo "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
-
Optionally remove the installed dependencies
sudo dnf remove -y xz
(5) [AppVM] Configure
bind-dirsfor 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
(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/userwon'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.servicehas a timeout of 60s. I was unable to setupbind-dirsfor 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 mybind-dirssetup.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-dirsfor 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-dirscorrupted, 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-qubesFedora package. Ideally it should be installed via Guix.
Outro
Thanks for reading, and happy reproducibilities.
Footnotes:
See docs: Setup passwordless root and networking agent.
Ref: See sys_create_store at Guix install.sh
Note that default is /nix/var/nix/profiles/per-user/root/profile.
rg '\.local/state/nix/' ./nixpkgs ./nixpkgs/nixos/modules/config/users-groups.nix: "$HOME/.local/state/nix/profile"
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.