Getting started with a GNU Guix VPS

This tutorial will let you edit the starting configuration of a GNU Guix VPS from You will be guided on how to make your VPS serve static content over HTTP, and then start a PostgreSQL instance. This guide expects some level of familiarity with the command line and a basic knowledge of networking, but is fairly accessible to beginners.

Advanced users will be able to adapt the procedure to their own goals.

Throughout this tutorial, we will link to the relevant sections of the GNU Guix Reference Manual. Its size can be daunting, but almost all the information you need is there. The little that is not there can be grasped by reading the source code.

Other community help resources exist such as the cookbook, the mailing lists, etc.

Establish a connection to the VPS

Soon after you subscribe, you will get an email with the IP of your VPS. For the sake of this tutorial, we will assume that the domain name points to this IP.

You can use the IP directly anywhere we use, or of course substitute your own domain name.

You should now be able to log in to the VPS with


provided you have loaded the private key corresponding to the public key you have given when you subscribed.

You will be greeted by the following prompt:

root@minimal-ovh ~#

You can close the shell (ctrl-D, or type exit), the goal was only to make sure you can connect to your VPS.

Get a copy of config.scm

The whole VPS is configured in one file, /etc/config.scm. This is a scheme file. Scheme is a programming language (see the crash course), which will come very handy once your configuration will become even moderately complex. The ability to store variables, loop over stuff, fetch information from other files, define your own operators, etc. is a huge boost over a simple markup language such as JSON or YAML.

For this tutorial, however, we will stick with simple operations, you don’t need to know scheme.

First, grab a copy of this file by running:

scp ./

This will copy the config.scm file into the current directory. I usually use git, a version control system, to keep a history of this file, and I think you should too.

When you open this file, it should look something like this:

(use-modules (gnu)
             (beaver system))
 (minimal-ovh "ssh-rsa AAASomethingSomething== root@minimal-ovh")
 ;; Your config goes here

The first two lines are akin to an import statement in Python, or a #include in C. The first argument to use-modules will import core operators (use-package-modules, etc.) that are needed under the hood. The second one imports some utilities written by Beaver Labs, the company behind, to make your life easier1.

Forget about the -> for a while (we will see what it means in the next section). One of Beaver Labs’ utilities is minimal-ovh. It is a function of one argument. The form:

(minimal-ovh "ssh-rsa AAASomethingSomething== root@minimal-ovh")

is scheme’s way of calling the function on what you will recognize as the SSH public key you gave when you subscribed to your VPS.

In an Algol-like syntax (such as Perl’s, Python’s, C’s, etc.), it would have been written as:

minimal_ovh("ssh-rsa AAASomethingSomething== root@minimal-ovh")

This function returns an operating system definition that GNU Guix understands, and which contains quite a lean configuration with everything needed to run GNU Guix on an OVH VPS (as of <2023-02-10 Fri> Beaver Labs’ underlying hosting provider is indeed OVH).

We will now see how to build on this lean configuration to make our VPS do something.

Serve static content through HTTP

Another of Beaver Labs’ utilities is the http-static-content function. It takes two mandatory arguments:

  • an operating system
  • a domain name.

It will return an operating system that serves the contents of /var/www/ when queried on port 80 via the given domain name.

So, for example, if you want to serve static content, you’ll change your /etc/config.scm to something that would be the same as the Algol-like:

    minimal_ovh("ssh-rsa AAASomethingSomething== root@minimal-ovh"),

Most of the utilities defined in the (beaver system) namespace work like this: the first argument is an operating system, and the return value is an operating system.

People familiar with functional programming will have seen this coming one section ago: one can chain such functions to add functionalities to the lean operating system we started with, until we are satisfied.

Indeed, if we wanted two subdomains, serving the contents of different subdirectories in /srv/, we would do something like:

        minimal_ovh("ssh-rsa AAASomethingSomething== root@minimal-ovh"),

But this is becoming a little bit hard to read. This is because Algol-like syntax, unlike scheme’s, is irregular and set in stone.

Scheme’s regular syntax alone would make matters only slightly better:

        (minimal_ovh "ssh-rsa AAASomethingSomething== root@minimal-ovh")
        #:from-host ""
        #:to-dir "/srv/sub1/")
    #:from-host "",
    #:to-dir "/srv/sub2")

Where scheme shines is with its homoiconicity, which allows for a powerful yet simple macro system. The -> macro2 allows us to chain functions seamlessly:

  • the first form after -> is the initial value,
  • every other form is a function call,
  • the return value from the previous function call will be placed as the first argument of the next function call.

Long story short, the full /etc/config.scm becomes, thanks to ->:

(use-modules (gnu)
             (beaver system))
 (minimal-ovh "ssh-rsa AAASomethingSomething== root@minimal-ovh")
 (http-static-content #:from-host "" #:to-dir "/srv/sub2")
 (http-static-content #:from-host "" #:to-dir "/srv/sub1/"))

And we are back to the simple and intuitive syntax one could expect from an Ansible playbook or a Dockerfile, but in a much more organic way, and with complete control over the underlying abstraction.

To activate this configuration, upload the file to the server, and reconfigure your system:

scp config.scm
ssh guix system reconfigure /etc/config.scm

If you are unhappy with the changes, no worries, you can always get back to where you were before with a simple call to:

guix system switch-generation -- -1

As of <2024-01-13 Sat>, Beaver Labs’ channel provides the following utility functions:

  • openssh-root-key,
  • packages,
  • http-reverse-proxy,
  • https-reverse-proxy,
  • http-static-content,
  • https-static-content,
  • mumble,
  • users,
  • groups,
  • os/mkdir-p
  • os/file
  • nobody-like-user,
  • ssh-user.
  • os/setuid
  • os/setcap
  • suc-private-channel
  • suc-public-channel
  • suc-dropbox-channel
  • os/git
  • os/hostname
  • os/permaudit
  • os/9mount
  • os/mount.9p
  • join-the-tilde-club
  • os/listen

Using GNU Guix’ extensible service system

Those utility functions cover only a fraction of the 266 services you can manage with GNU Guix. In order to let you use a service that is not covered by a utility function, Beaver Labs’ channel also provide syntactic sugar to let you instantiate of extend any of GNU Guix’s service.

The manual gives a thorough explanation of how services work and can be used. We will show you here the quick way of adding a service to your VPS, but as you go on with GNU Guix, we strongly suggest you dive in, as the service system is really quite nice and clever.

Anyway, let’s assume for example you want to run an instance of the PostgreSQL database.

You can Ctrl-F your way through the manual, or you can also invoke:

guix system search postg

which will show you that there is a PostgreSQL service available:

name: postgresql
location: gnu/services/databases.scm:320:2
extends: shepherd-root activate account profile
shepherdnames: postgres
description: Run the PostgreSQL database server.
relevance: 5

Using Beaver Labs’ syntactic sugar, just add the following line to your VPS configuration:

(add-service postgresql)

Or maybe you want to change where the log will be stored, of which exact version of PostgreSQL is used. You can see in the manual that postgresql-configuration has a log-directory and a postgresql fields that can be set. The line service then becomes:

(add-service postgresql
 (postgresql postgresql-10)
 (log-directory "/var/log/db")))

Now, you just need to

  • add (beaver functional-services) to your imports at the top of the /etc/config.scm file
  • and add the service to the operating system.

You also need to tell GNU Guix where to find postgresql-10 which is the package you wish to use (this lets you specify the version you want).

In order to know where it is, run:

guix search postgresql

And you will find it:

name: postgresql
version: 10.23
outputs: out: everything
systems: x86_64-linux i686-linux
dependencies: docbook-sgml@4.2 docbook2x@0.8.8 libxml2@2.9.12 opensp@1.5.2 openssl@1.1.1l perl@5.34.0 readline@8.1.1 texinfo@6.7 util-linux@2.37.2 zlib@1.2.11
location: gnu/packages/databases.scm:1330:2
license: X11-style
synopsis: Powerful object-relational database system
description: PostgreSQL is a powerful object-relational database system.  It is fully ACID compliant, has full support for foreign keys, joins, views, triggers, and stored procedures (in multiple languages).  It includes most SQL:2008 data types, including INTEGER, NUMERIC, BOOLEAN, CHAR, VARCHAR, DATE, INTERVAL, and TIMESTAMP.  It also supports storage of binary large objects, including pictures, sounds, or video.
relevance: 32

The location field tells you what to import:

(use-modules ... (gnu packages databases) ... )

The final /etc/config.scm is now:

(use-modules (gnu)
             (beaver system)
             (beaver functional-services)
             (gnu packages databases))

 (minimal-ovh "ssh-rsa AAASomethingSomething== root@minimal-ovh")
 (http-static-content #:from-host "" #:to-dir "/srv/sub2")
 (http-static-content #:from-host "" #:to-dir "/srv/sub1/")
 (add-service postgresql (postgresql postgresql-10) (log-directory "/var/log/db")))

Don’t forget to enforce the changes by running

scp config.scm
ssh guix system reconfigure /etc/config.scm

Next steps

You can now browse the list of services ready to use with GNU Guix and add any of those to your VPS.

Keep your config.scm file under version control, and you will be able to reproduce the same configuration on any guix machine, and even generate VMs and containers with the same configuration.

If you want to keep exactly the same version of every piece of software, down to a bit-by-bit exact replica, don’t forget to put the output of:

guix describe --format=channels

under version control as well. This plus your config.scm will be more or less what a pip freeze or npm’s package-lock.json does. But for your whole system.

The next step will be to learn how to package your own software and run it as a service, with the option to isolate it in a container for better security.


  • <2024-01-13 Sat> Changed the explanation of the syntactic sugar to use the new syntax
  • <2023-06-27 Tue> Add a brief explanation of the .foo syntactic sugar for services
  • <2023-02-10 Fri> Initial version



This means that Beaver Labs’ channel has been added to the default configuration. Adding channels willy-nilly is a security risk, but we are your hosting provider, and anything we could do via a channel, we could do more discreetly by other means, you have to trust us somewhat (as you technically need to trust any hosting provider).


Which I first encountered when I was learning Clojure, and the scheme implementation of which was lifted from this gist: