+++ date = "2023-04-29T02:10:18+00:00" publishdate = "2023-12-29T07:08:55+00:00" title = "Guix in a Linux Container" slug = "guix-in-a-linux-container" author = "Thedro" tags = ["linux"] type = "posts" summary = "GNU Guix (as in geeks) is one of the new type of Linux distributions that focuses on reproducibility." draft = "" syntax = "1" toc = "" updated = "2023-05-02" +++ ![GNU Guix homepage](/images/guix-in-a-linux-container.png " GNU Guix [homepage](https://guix.gnu.org/)" ) [`GNU Guix`](https://guix.gnu.org/) (as in geeks) is one of the new type of [Linux distributions](https://en.wikipedia.org/wiki/Linux_distribution) that focuses on reproducibility. It's in the class of systems that can allow for immutability, in addition to atomic updates and `root` less user package management. [`NixOS`](https://nixos.org/) is another similar new type in this class of reproducible operating systems. Placing a Linux distribution into a container is a good way to learn about the underlying structure before installing it onto a real device. This {{< sidenote mark="time" set="right" >}} The last time was [`NixOS` in a Linux container](/posts/running-nixos-linux-containers/). {{< /sidenote >}} let's play around with `Guix`. ## Commands The commands in this post are for running on [`Debian`](https://www.debian.org/) but it's possible on any Linux distribution via [the `Guix` shell installer script](https://guix.gnu.org/manual/en/html_node/Binary-Installation.html). `Debian` makes this easy because it has a `guix` package in its repository. It may be necessary to prefix `sudo` to the following commands for `root` privileges. ```shell apt install guix ``` There are other packages that might be required for `guix` to work correctly. ```shell apt install netbase gpg xz-utils ca-certificates ``` `Guix` needs a daemon to build and answer different requests. Running it [without `root`](https://hpc.guix.info/blog/2017/10/using-guix-without-being-root/) is possible (from a container point of view). Start the daemon if it's not running. ``` guix-daemon & ``` From there `guix` is now installed. Running [`guix pull`](https://guix.gnu.org/manual/en/html_node/Invoking-guix-pull.html) brings in {{< sidenote mark="updated" set="left" >}} Running the `pull` subcommand is an important step --- it's best to be in sync with upstream at least once before starting anything configuration related. {{< /sidenote >}} sources and [upgrades](https://guix.gnu.org/manual/en/html_node/Upgrading-Guix.html) `guix` to the latest version. ```shell guix pull ``` The version string should reflect the [current `git` repository](https://git.savannah.gnu.org/cgit/guix.git/log/) commit at the time of pulling. In this post the `guix` version is the short commit identifier `3ab983d`. ```shell $ guix --version guix (GNU Guix) 3ab983d630a95a29b9418b1ba8a26e5ca2836ec0 ``` If the version string of `guix` isn't up to date then check that the shell has properly sourced the `guix` specific environment variables like `PATH` from the home folder. ```shell . /etc/profile.d/guix.sh ``` In other instances, it might be necessary to hash or update the new location of `guix` using the built--in shell command `hash`. ```shell hash guix ``` To view the exact revision the system is running use the [describe](https://guix.gnu.org/manual/en/html_node/Invoking-guix-describe.html) subcommand. ``` $ guix describe Generation 1 Apr 11 2023 22:06:05 (current) guix 3ab983d repository URL: https://git.savannah.gnu.org/git/guix.git branch: master commit: 3ab983d630a95a29b9418b1ba8a26e5ca2836ec0 ``` [Searching for programs](https://guix.gnu.org/manual/en/html_node/Invoking-guix-package.html) to install locally on `Guix` is powerful. There's also a portal for previewing [the available packages online](https://packages.guix.gnu.org/). ## Overview The [`Guix` configuration system](https://guix.gnu.org/manual/en/html_node/Using-the-Configuration-System.html) works from a declared file that represents the desired state of the machine. That file, let's say `config.scm` needs to describe a configuration in [Guile Scheme](https://www.gnu.org/software/guile/) that returns an `operating-system`. ```scheme {caption="config.scm"} (use-modules (gnu)) (operating-system) ``` `Guix` already comes with a [system subcommand](https://guix.gnu.org/manual/en/html_node/Invoking-guix-system.html) that allows creating varied formats from a defined configuration for containers, images, and virtual machines (`vm`). ```shell {caption="Generating containers, images, and virtual machines with guix"} guix system container config.scm guix system image --image-type=tarball config.scm guix system vm config.scm ``` The list below shows the various presets for different image output format types. ```shell {caption="The various config.scm image output types"} $ guix system image --list-image-types The available image types are: - rock64-raw - pinebook-pro-raw - pine64-raw - novena-raw - hurd-raw - hurd-qcow2 - efi32-raw - qcow2 - iso9660 - wsl2 - docker - uncompressed-iso9660 - tarball - efi-raw - raw-with-offset ``` All of these work great and are extremely useful. In my case however, I'll explore a bit further for a file system that can be plugged into a few more places like a [`LXC`](https://linuxcontainers.org/lxc/introduction/) (Linux Containers). A brief [look around the net](https://guix-devel.gnu.narkive.com/CWhVGj4c/guixsd-in-lxd-container) seems [to suggest](https://lists.gnu.org/archive/html/guix-devel/2016-12/msg00777.html) that there are multiple approaches. But nothing beats source code right? The `guix` system container commands appear to run from the [`gnu/system/linux-container.scm`](https://git.savannah.gnu.org/cgit/guix.git/tree/gnu/system/linux-container.scm?id=b8152d668d16faa464d2819af6f8ed4b2637538b#n147) module definition. {{< sidenote mark="Trawling" set="right" >}} I'm a scheme neophyte. {{< /sidenote >}} the source further reveals a [`gnu/system/examples`](https://git.savannah.gnu.org/cgit/guix.git/tree/gnu/system/examples?id=b8152d668d16faa464d2819af6f8ed4b2637538b) folder that lists different configuration templates including a [`docker-image.tmpl`](https://git.savannah.gnu.org/cgit/guix.git/tree/gnu/system/examples/docker-image.tmpl?id=b8152d668d16faa464d2819af6f8ed4b2637538b) and a [`vm-image.tmpl`](https://git.savannah.gnu.org/cgit/guix.git/tree/gnu/system/examples/vm-image.tmpl?id=b8152d668d16faa464d2819af6f8ed4b2637538b). The [`linux-container.scm`](https://git.savannah.gnu.org/cgit/guix.git/tree/gnu/system/linux-container.scm?id=b8152d668d16faa464d2819af6f8ed4b2637538b#n147) looks {{< sidenote mark="approachable" set="left" >}} If there's enough programming experience to make out the language constructs. If not --- [`Lisp` in `Y` minutes](https://learnxinyminutes.com/docs/common-lisp/) can help, maybe? Maybe not? {{< /sidenote >}} enough to massage into a `config.scm` with a bit of guess work. `Guix` also has a [`%base-services`](https://guix.gnu.org/manual/en/html_node/Base-Services.html) list in [`gnu/services/base.scm`](https://git.savannah.gnu.org/cgit/guix.git/tree/gnu/services/base.scm?id=b8152d668d16faa464d2819af6f8ed4b2637538b#n3315) that can work as a reference to build up a minimal [service configuration](https://guix.gnu.org/manual/en/html_node/Shepherd-Services.html). [`GNU` Shepherd](https://www.gnu.org/software/shepherd/manual/shepherd.html#Jump-Start-1) is the program that starts up the `guix` system. `Guix` system [`init`](https://guix.gnu.org/manual/en/html_node/Invoking-guix-system.html) sets up a target directory with the needed files to start the `OS` (Operating System). A `--dry-run` tests the configuration file for correctness. The `--no-bootloader` argument is passed because well it's not a real device --- not yet at least. ```shell mkdir filesystem guix system --dry-run init --no-bootloader config.scm filesystem/ ``` ## Configuration A setup more suited for a container is added to the [`operating-system`](https://guix.gnu.org/manual/en/html_node/operating_002dsystem-Reference.html) {{< sidenote mark="configuration" set="left" >}} [GNU Emacs](https://www.gnu.org/software/emacs/) is apparently the editor best suited to wrangle anything [Lisp--like](). To indent everything press `M-x` or `Alt-x`, then type `mark-whole-buffer` and finally hit `TAB`. {{< /sidenote >}} based on the previously mentioned `linux-container.scm` source file. ```scheme { options="hl_lines=14-17",caption="config.scm" } (use-modules (gnu) (gnu system locale)) (use-service-modules networking ssh) (use-package-modules ssh bash package-management) (operating-system (host-name "guix") (timezone "America/Nassau") (locale "en_US.utf8") (firmware `()) (initrd-modules `()) (kernel hello) (packages (list guix coreutils)) (essential-services (modify-services (operating-system-default-essential-services this-operating-system) (delete firmware-service-type) (delete (service-kind %linux-bare-metal-service)))) (locale-definitions (list (locale-definition (name "en_US.utf8") (source "en_US") (charset "UTF-8")))) (bootloader (bootloader-configuration (bootloader grub-bootloader) (targets '("/dev/null")))) (file-systems (list (file-system (device "/dev/null") (mount-point "/") (type "dummy")))) (services (list (service dhcp-client-service-type) (service syslog-service-type) (service guix-service-type) (service static-networking-service-type (list %loopback-static-networking)) (service special-files-service-type `(("/bin/sh" ,(file-append bash "/bin/sh")) ("/usr/bin/env" ,(file-append coreutils "/bin/env")))) (service udev-service-type (udev-configuration (rules '()))) (service openssh-service-type (openssh-configuration (openssh openssh-sans-x)))))) ``` Generate the files needed to boot the system by running the `init` subcommand without a `--dry-run`. ``` guix system init --no-bootloader config.scm filesystem/ ``` One of the best ways to pass around file systems like `guix` with hard links and such is to use [`rsync`](https://man.archlinux.org/man/rsync.1). ```shell rsync -aAXHv filesystem/ rootfs rsync --archive --acls --xattrs --hard-links --verbose filesystem/ rootfs ``` ## Container The configuration below contains `guix` specific options that allow the container to start up. ```ini { options="hl_lines=12-17",caption="~/.local/share/lxc/guix/config" } # Distribution configuration lxc.arch = linux64 lxc.include = /usr/share/lxc/config/common.conf # Network configuration lxc.net.0.type = veth lxc.net.0.link = lxcbr0 lxc.net.0.flags = up lxc.net.0.hwaddr = 00:16:3e:48:0d:f8 # Container configuration lxc.uts.name = guix lxc.environment = HOME=/root lxc.environment = GUIX_NEW_SYSTEM=/gnu/store/n3yd660amqnhcmyv3qj2ghc4xhnfcj5z-system lxc.rootfs.path = dir:/home/thedro/.local/share/lxc/guix/rootfs lxc.init.cmd = /gnu/store/vbcdwklck7cd28j4jv9wchfw60abivfd-guile-3.0.9/bin/guile /gnu/store/hgi3i9ydjhi5bxnpz7zmzaqn60iw3ak1-boot ``` `Guix` will try to [activate-modprobe](https://git.savannah.gnu.org/cgit/guix.git/tree/gnu/build/activation.scm?id=b8152d668d16faa464d2819af6f8ed4b2637538b#n352) and [activate-firmware](https://git.savannah.gnu.org/cgit/guix.git/tree/gnu/build/activation.scm?id=b8152d668d16faa464d2819af6f8ed4b2637538b#n362). If those activation paths (`proc` or `sys`) are exposed to the container and are read only, then the container may fail to start. The work around is to remove the activation scripts by modifying the list of `essential-services` in the `operating-system` as seen in the [configuration from before](#code-block-ca26718). In addition, the `lxc.init.cmd` has to be populated with the system environment variable (`GUIX_NEW_SYSTEM`), the boot script path, and the `guile` executable path needed to start the system. Setting up a `special-files-service-type` for an equivalent to `/sbin/init` is probably a better approach after the first boot, but for now manually locating the paths with the [`find`](https://man.archlinux.org/man/find.1) command from the `root` directory works well. ```shell find . -maxdepth 3 -regex '.*system' -print -quit find . -maxdepth 3 -regex '.*boot' -print -quit find . -maxdepth 3 -regex '.*guile-3.*' -print -quit ``` Entering the container with `lxc-attach` may require sourcing `/etc/profile` to populate all of the `guix` specific environment variables. ```shell . /etc/profile ``` ## Comparisons The `GNU Guix` system is inspired by `NixOS` and there are some initial comparisons that can be made between the two within the context of containers. In terms of file sizes for the containers, consider the following shaved down `NixOS` container configuration. ```nix { caption="A container configuration.nix for NixOS" } { imports = [ ]; disabledModules = [ ]; system.stateVersion = "22.11"; nixpkgs.system = "x86_64-linux"; networking.hostName = "nixos-container"; environment.defaultPackages = [ ]; boot.isContainer = true; xdg.mime.enable = false; xdg.icons.enable = false; xdg.sounds.enable = false; users.mutableUsers = false; documentation.enable = false; security.polkit.enable = false; fonts.fontconfig.enable = false; services.udisks2.enable = false; nixpkgs.config.allowUnfree = true; } ``` At a quick glance, operations on `Guix` are slightly slower than `NixOS` but as seen from both configurations `Guix` is much easier to hack on and with naive attempts at minimization clocks in at a {{< sidenote mark="lower" set="right" >}} Not exactly definitive but you get the idea. {{< /sidenote >}} size. ```shell $ du -h guix 834M guix/ ``` ```shell $ du -h nixos 1.1G nixos/ ``` `Guix` also runs in an unprivileged container easily as shown in the video below but for `NixOS` it's rather difficult to do. ## Conclusion That's a wrap. {{< sidenote mark="Container" set="right" >}} And more preferably virtual machines. {{< /sidenote >}} setups allow for experimenting with lots of configurations and software before going all in on real devices! On another note --- here's a [quick overview and rundown of the `Guix`](https://rendaw.gitlab.io/blog/55daefcf49e2.html) system. {{< video poster="/images/guix-in-a-linux-container-poster.png" source="/videos/guix-in-a-linux-container.mp4" options="loop muted" width="820" >}} Poking around the container to run the `guix` command and as a bonus [neofetch](https://man.archlinux.org/man/neofetch.1) {{< /video >}}