Since I have discovered the existence of nix, I have slowly but surely nixified all the things around me: personal projects, machine configurations both on NixOS and on top of Ubuntu, my RPI, testing VMs and so on.
The last thing I needed to nixify was the deployment of this website. The project itself was already nixified a long time ago.
Have NixOS running on a VM
The first thing to do is to have NixOS running on a VM. I personally use Digital Ocean to host my website. One can create a NixOS image for Digital Ocean with a nix expression such as:
{ pkgs ? import <nixpkgs> { } }:
let
config = {
imports = [ <nixpkgs/nixos/modules/virtualisation/digital-ocean-image.nix> ];
};
in
(pkgs.nixos config).digitalOceanImage
If you save this expression into a file called image.nix
, then a call to
$ nix build --file ./image.nix
will build the image in the file ./result/nixos.qcow2.gz
.
That file can then be uploaded to Digital Ocean and used to create a new VM. When you do so, make sure you select your SSH key to be able to connect to the VM once it is created.
Alternative: use nixos-infect
Create a derivation for the website
Now we need a derivation with the website content. The steps to achieve this are:
- build the Hakyll executable
- run it to make it create the actual webpages
- get the result and put it in the nix store
Here are the relevant lines in flake.nix
:
(final: prev:
overlay = {
site = final.haskellPackages.callCabal2nix "site" ./. { };
jeancharles-quillet = final.stdenv.mkDerivation {
name = "jeancharles-quillet";
src = ./.;
LC_ALL = "C.UTF-8";
buildInputs = [ final.site ];
buildPhase = ''
${final.site}/bin/site build
'';
doCheck = true;
checkPhase = ''
${final.site}/bin/site check
'';
installPhase = ''
cp -r _site $out
'';
};
});
This overlay defines two derivations:
site
: the Hakyll executable, built withcallCabal2nix
jeancharles-quillet
: the webpages generated by thesite
program created by the previous derivation
Create the NixOS configuration
Now we need a NixOS configuration that setups a webserver which will serve our website content.
Check out this simple configuration.nix
:
{ pkgs, modulesPath, ... }:
{
console.keyMap = "fr";
services.nginx.enable = true;
services.nginx.virtualHosts."jeancharles.quillet.org" = {
enableACME = true;
forceSSL = true;
root = "${pkgs.jeancharles-quillet}";
};
services.nginx.virtualHosts."quillet.org" = {
globalRedirect = "jeancharles.quillet.org";
enableACME = true;
addSSL = true;
serverAliases = [ "www.quillet.org" ];
};
security.acme.acceptTerms = true;
security.acme.certs."jeancharles.quillet.org".email =
"jeancharles.quillet@gmail.com";
security.acme.certs."quillet.org".email = "jeancharles.quillet@gmail.com";
networking.firewall.allowedTCPPorts = [ 22 80 443 ];
services.sshd.enable = true;
system.stateVersion = "22.11";
}
That is it, the full configuration of the VM:
- we enable nginx service
- use acme for getting a certificate for the domain names
- we open the required ports
- and finally point the root of the virtual host to the derivation that contains the pages themselves
- also we enable the
sshd
service to be able to connect to the machine through SSH
Warp everything in the flake
All this is packaged together in this flake output:
-prod = nixpkgs.lib.nixosSystem {
nixosConfigurations.websitesystem = "x86_64-linux";
modules = [
({ ... }: { nixpkgs.overlays = [ self.overlay ]; })
./configuration.nix
]
++
(nixpkgs.lib.optional (builtins.pathExists ./do-userdata.nix) ./do-userdata.nix ++ [
(nixpkgs + "/nixos/modules/virtualisation/digital-ocean-config.nix")
]);
};
Deploy
Now deploying the website is as simple as:
$ nixos-rebuild switch --flake .#website-prod --target-host quillet.org
The configuration will be built on the local machine, then uploaded to the host and the relevant services will be restarted.
And that’s it! Now the webserver can be updated at any time with all the reproducibility guaranties that nix provides.
This is basically how this website is deployed. You can have a look at the details in the repository itself.