Build a Jekyll blog with Nix using flakes
This blog is powered by Github pages and jekyll. I started it when I had never ever heard of Nix, but since then I’ve moved to NixOS and wanted to be able to write again here. Unfortunately it wasn’t that easy…
Indeed the documentation out here about building this setup with Nix is very outdated and a lot of it just doesn’t work anymore because package evolved, purity is now enforced using flakes, etc …
I’ve managed to get something to work and I’ll write it down for you:
Base flake structure
The base structure of the flake is fairly simple:
{
description = "Build the blog";
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/22.11";
};
outputs = inputs: with inputs; let
system = "x86_64-linux";
pkgs = import nixpkgs { inherit system; };
# Utility to run a script easily in the flakes app
simple_script = name: add_deps: text: let
exec = pkgs.writeShellApplication {
inherit name text;
runtimeInputs = with pkgs; [
gnumake # Required by some dependencies in order to build
bundler
] ++ add_deps;
};
in {
type = "app";
program = "${exec}/bin/${name}";
};
in {
apps.${system} = {
default = simple_script "serve_blog" [] ''
# nix run -> Serve the website locally
'';
generate = simple_script "generate_blog_env" [] ''
# nix run .#generate
# Will be used to re-generate the Ruby environment after modifying
# a dependency, the version number, etc ...
'';
};
};
}
The workflow
At initialisation, and after that every time you modify a dependency, bump a version, or modify something in the Ruby environment, call nix run .#generate
It will use bundler
and bundix
to generate the Gemfile
, Gemfile.lock
, gemset.nix
and even .bundle/config
.
This way, we only need to configure variables inside the flake, the rest will magically work.
Once this is done, you can serve the website locally by calling nix run
It will use the Ruby environment we created using the generate
script, and call jekyll serve --trace
Generating the Ruby environment
First for the environment generation, we will generate the Gemfile
file using good ol’ nix variables:
github-pages-version = 227;
inc_gems = {
minima = "2.5";
webrick = "1.7"; # Required for the jekyll serve to work
};
inc_plugins = {
jekyll-feed = "0.12";
};
# Generation of the Gemfile
generate_gemfile = let
gems = builtins.concatStringsSep "\n" (pkgs.lib.attrsets.mapAttrsToList (name: version:
"gem \"${name}\", \"~> ${version}\""
) inc_gems);
plugins = builtins.concatStringsSep "\n" (pkgs.lib.attrsets.mapAttrsToList (name: version:
" gem \"${name}\", \"~> ${version}\""
) inc_plugins);
in
''
# This file is autogenerated by the flake.nix file, do not edit
source "https://rubygems.org"
# Gems dependencies to be installed
${gems}
gem "github-pages", "~> ${builtins.toString github-pages-version}", group: :jekyll_plugins
# Github Pages plugins
group :jekyll_plugins do
${plugins}
end
'';
This Gemfile
will be used by bundler
to create a Gemfile.lock
to pin the dependencies, which in turn will be used by bundix
to generate a gemset.nix
, the nixified ruby gems.
All these files are used by the nix bundlerEnv
function like this:
# The build environment
env = pkgs.bundlerEnv {
name = "blog";
ruby = pkgs.ruby_3_1;
gemdir = ./.;
};
Now, let’s tie the two pieces together in the generate_blog_env
script:
generate = simple_script "generate_blog_env" [ pkgs.bundix ] ''
set -e
rm -f gemset.nix Gemfile Gemfile.lock .bundle/config
cat << EOF > Gemfile
${generate_gemfile}
EOF
bundler update
bundler lock
bundler package
bundix --magic -d -l
rm -rf vendor
'';
So far so good.
Bundle configuration and platform issues
Nowadays, bundler
doesn’t like command line flags to be used, and it prefers to read from either the .bundle/config
file, or directly from environment variables.
In the generate_blog_env
script, I adapted the flags passed to generate the configuration before generating the gemset.nix
file like so:
export BUNDLE_PATH=vendor
export BUNDLE_CACHE_ALL=true
export BUNDLE_NO_INSTALL=true
export BUNDLE_FORCE_RUBY_PLATFORM=true
Notice the BUNDLE_FORCE_RUBY_PLATFORM
, it is needed because some dependencies (at least nokogiri
) are platform-specific and will raise an issue if it detects the machine platform. This configuration will ensure that it won’t raise any compatibility issue.
Using the environment
Yay ! The environment builds without any trouble !
Now let’s serve the website locally using jekyll
. In the serve_blog
script, we simply have to write:
''
echo "Bundler env: ${env}" # For debugging / exploration of the files
${env}/bin/bundler exec ${env}/bin/jekyll serve --trace
''
And now we can serve the whole thing locally without any troubles !
It finally builds
The full flake source can be seen here
All this setup is the result of hours or research and debugging, and I unfortunately wasn’t able to find all the links and pages that I used, so I cannot credit them the way I’d like to. However a big Thank you is in order for all these people
This setup builds perfectly for my (weird) setup, but it’s far from clean, optimized, and could maybe not work for you.
However feel free to reach me if you have some trouble, or if you wish to make this setup better, for now it only lies in my blog repo, but why not having a repository specially made for building a Github pages + jerkyll setup using Nix.