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 installation of Guix, and even below, the Nix steps, which you can append or replace. It works together too.
Steps for Installation
- [dom0] Create TemplateVM and connect the NetVM to it. In my case, called
fedora-42-minimal-guix - Setup passwordless root and networking agent
[dom0] Open a root shell at the template
qvm-run -u root fedora-42-minimal-guix xterm
[TemplateVM] Then install on the template the passwordless and networking Qubes agent:
dnf install -y qubes-core-agent-passwordless-root qubes-core-agent-networking
You can now close your root terminal and open xterm from dom0 applications menu to get a user shell.
[TemplateVM] Install hard dependencies for Guix installation
sudo dnf install -y wget xz # needed for retrieving `latestArtifactUrl` in below script sudo dnf install -y xq
- [TemplateVM] Install latest Guix build by script (should be root)
Install it using latest tarball and installation script
latestArtifactUrl=$(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{}') && cd /tmp && wget -O guix-install.sh https://guix.gnu.org/install.sh && wget -O guix-binary.tar.xz "$latestArtifactUrl" && chmod +x guix-install.sh && GUIX_BINARY_FILE_NAME=guix-binary.tar.xz ./guix-install.sh
Update Guix Using Codeberg because
git.guix.gnuis now down for me.guix pull --url=https://codeberg.org/guix/guix
[TemplateVM] Setup the AppVM base home by linking the user profile.1
sudo mkdir -p /etc/skel/.config/guix/ sudo ln -s /var/guix/profiles/per-user/user/current-guix /etc/skel/.config/guix/current
- [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 hard dependencies for Nix intallation.
sudo dnf install -y xz
(4) [TemplateVM] Install via Nix installation script
sh <(curl --proto '=https' --tlsv1.2 -L https://nixos.org/nix/install) --daemon- (5) [TemplateVM] Setup the AppVM base home
Link the profile2
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: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
(7.1) [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
(7.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. 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.
Outro
Thanks for reading, and happy reproducibilities.
Footnotes:
Ref: See sys_create_store at Guix install.sh
Note that default is /nix/var/nix/profiles/per-user/root/profile.