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 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
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 pull
The 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) 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 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: 3ab983d630a95a29b9418b1ba8a26e5ca2836ec0
Searching 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.scm
The 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-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
(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/ rootfs
Container
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-boot
Guix
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 -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
{
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