Guix in a Linux Container
GNU Guix (as in geeks) is one of the new type of
Linux distributions 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 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
NixOS in a Linux container.
Guix.
Commands
The commands in this post are for running on Debian
but it’s possible on any Linux distribution via
the Guix shell installer script.
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 guixThere are other packages that might be required for guix to work correctly.
shell
apt install netbase gpg xz-utils ca-certificatesGuix needs a daemon to build and answer different requests. Running it
without root
is possible (from a container point of view). Start the daemon if it’s not running.
text
guix-daemon &From there guix is now installed. Running
guix pull
brings in
pull subcommand is an important step — it’s best to be in sync with
upstream at least once before starting anything configuration related.
guix to the latest version.
shell
guix pullThe version string should reflect the
current git repository
commit at the time of pulling. In this post the guix version is the short
commit identifier 3ab983d.
shell
$ guix --version
guix (GNU Guix) 3ab983d630a95a29b9418b1ba8a26e5ca2836ec0If 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.shIn other instances, it might be necessary to hash or update the new location of
guix using the built–in shell command hash.
shell
hash guixTo view the exact revision the system is running use the describe subcommand.
text
$ 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: 3ab983d630a95a29b9418b1ba8a26e5ca2836ec0Searching for programs
to install locally on Guix is powerful. There’s also a portal for previewing
the available packages online.
Overview
The
Guix configuration system
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 that returns an
operating-system.
scheme
(use-modules (gnu))
(operating-system)Guix already comes with a
system subcommand
that allows creating varied formats from a defined configuration for containers,
images, and virtual machines (vm).
shell
guix system container config.scm
guix system image --image-type=tarball config.scm
guix system vm config.scmThe list below shows the various presets for different image output format types.
shell
$ 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-offsetAll 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 (Linux
Containers). A brief
look around the net
seems
to suggest
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
module definition.
gnu/system/examples
folder that lists different configuration templates including a
docker-image.tmpl
and a
vm-image.tmpl.
The
linux-container.scm
looks
Lisp in Y minutes
can help, maybe? Maybe not?
config.scm with a bit of
guess work. Guix also has a
%base-services
list in
gnu/services/base.scm
that can work as a reference to build up a minimal
service configuration.
GNU Shepherd
is the program that starts up the guix system.
Guix system
init
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
M-x or Alt-x, then type mark-whole-buffer and
finally hit TAB.
linux-container.scm source file.
scheme
(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.
text
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.
shell
rsync -aAXHv filesystem/ rootfs
rsync --archive --acls --xattrs --hard-links --verbose filesystem/ rootfsContainer
The configuration below contains guix specific options that allow the
container to start up.
ini
# 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-bootGuix will try to
activate-modprobe
and
activate-firmware.
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.
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 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 -quitEntering the container with lxc-attach may require sourcing /etc/profile to
populate all of the guix specific environment variables.
shell
. /etc/profileComparisons
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
{
imports = [
<nixpkgs/nixos/modules/profiles/headless.nix>
<nixpkgs/nixos/modules/profiles/minimal.nix>
];
disabledModules = [
<nixpkgs/nixos/modules/profiles/all-hardware.nix>
<nixpkgs/nixos/modules/profiles/base.nix>
];
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
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.
Guix
system.
guix command and as a bonus
neofetch