How to set up a nix-backed blog (ft. Hugo, Gitlab CI/CD and Netlify)

This goes over how I set up a static website using Nix, Hugo, Gitlab CI/CD and Netlify.

The argument for having a nix-backed blog is the usual argument for using nix to build anything - by leveraging the nix package manager to build the dependencies of a project, the project should be completely portable and reproducible no matter where you build it. Nix philosophy aside, I wanted this website to be a nix-backed project because I’m running NixOS on my daily driver and I’m still getting the hang of using nix for development.

That said, here are the basic requirements for my blog:

  • dependencies should be managed by nix
  • source code should be in a private repo
  • new blog posts (via commits) should be automatically published

First, have a blog

My starting point was this post from kalbas.it, where I grabbed the initial nix-shell script to bootstrap my hugo project. The og post includes a more detailed breakdown of how the script works and how to pin nixpkgs and a hugo theme of your choice to a specific commit so that it’s reproducible between builds.

My shell.nix, which sits at the root of my project dir, ended up looking like this:

let

  # See https://nixos.wiki/wiki/FAQ/Pinning_Nixpkgs for more information on pinning
  nixpkgs = builtins.fetchTarball {
    name = "nixpkgs-20.09"; # Store path name 
    url = https://github.com/NixOS/nixpkgs/archive/cd63096d6d887d689543a0b97743d28995bc9bc3.tar.gz; # Commit hash for nixpkgs 20.09 release
    sha256 = "1wg61h4gndm3vcprdcg7rc4s1v3jkm5xd7lw8r2f67w502y94gcy"; # Hash obtained using `nix-prefetch-url --unpack <url>`

  };

in

{ pkgs ? import nixpkgs {} }:

with pkgs;

let
  hugo-theme-cactus = runCommand "hugo-theme-cactus" {
    pinned = builtins.fetchTarball {
      name = "hugo-theme-cactus-2020-12-28"; # Store path name
      url = https://github.com/monkeyWzr/hugo-theme-cactus/archive/f3fe7410c3839e7685812600dc8d2ae54de85d4e.tar.gz;
      sha256 = "1mpqhsbgxvcg0z5jnmcxyyk2rhk3w6bm16cwbyp77x7rq7ggqr3x"; # Hash obtained using `nix-prefetch-url --unpack <url>`

    };

    patches = [];

    preferLocalBuild = true;
  }
  ''
    cp -r $pinned $out
    chmod -R u+w $out

    for p in $patches; do
      echo "Applying patch $p"
      patch -d $out -p1 < "$p"
    done
  '';
in

mkShell {
  buildInputs = [
    hugo
  ];

  shellHook = ''
    mkdir -p themes
    ln -snf "${hugo-theme-cactus}" themes/hugo-theme-cactus
  '';
}

Now to generate the initial instance of my blog, I run:

$ nix-shell --run "hugo new site coolsite" 

// move generated dirs into the project root
$ cp -r coolsite/* . & rm -rf coolsite  

You can “hugo serve” (inside of a nix shell!) to see your website in action locally.

Continuously deliver this thing

Revisiting the goal, I want to set things up so that publishing new blog posts to the internet requires minimal effort. To achieve this I’m using Gitlab for CI/CD and Netlify for hosting. Gitlab because I wanted to host my code from a private repo (this is paid feature for Github Pages apparently?), and Netlify because they have integration with Gitlab and netlify-cli is supported in the nixpkgs version I point at.

Setting up Gitlab CI/CD

To configure CI/CD, I have a .gitlab-ci.yml in the root of my project that looks like this:

image: nixos/nix

stages:
  - build
  - deploy

cache:
  paths:
    - /nix/store

build:
  stage: build
  script:
    - nix-shell --command "hugo"
  artifacts:
    untracked: true
    paths: 
      - public/

deploy:
  stage: deploy
  script:
    - nix-shell -p netlify-cli --command "netlify deploy --auth=$NETLIFY_AUTH_TOKEN --dir=public --site=$APP_ID --prod"
  when: on_success

This describes a nixos-backed build image with two stages: build and deploy. Each call to nix-shell will first evaluate the shell.nix we set up earlier, which ensures that the exact version of nixpkgs and the hugo theme are used with each build.

The build stage calls “hugo” which generates the public directory. The deploy step uses the netlify cli to publish the site described in public/ to netlify.

Before this can work, you’ll have to set up the $NETLIFY_AUTH_TOKEN and $APP_ID environment variables in Gitlab.

Pointing at Netlify

First you’ll want to create a Netlify account and link your Gitlab repo if you haven’t already1.

You can grab your auth token by running the netlify-cli locally. In your nix-shell, run:

$ nix-shell -p netlify-cli --command "netlify login"

You should now be able to grab your auth token from your local netlify config file, ie the token line in ~/.netlify/config.

The last variable you need is the app id of your netlify site, which you can grab from the “General” page of your site’s dashboard on netlify. These values can now be plugged into Gitlab as $NETLIFY_AUTH_TOKEN and $APP_ID2.

You’re done!

You should now be able to push a commit and watch Gitlab CI/CD build and deploy your website on its own. All backed by nix! Noice.


  1. https://gohugo.io/hosting-and-deployment/hosting-on-netlify ↩︎

  2. https://docs.gitlab.com/ee/ci/variables/#create-a-custom-variable-in-the-ui. ↩︎