6 min read

#nixos
Minimal self-updating NixOS Flake built for Proxmox LXC .tar.gz stored in Forgejo

Greg Dosh

Technical Coach / Mentor

I've been recently playing around with some NixOS & Proxmox LXC deployments to simplify parts of my on-prem server architecture. Creating the LXC has been one aspect, and then learning more nuanced CI/CD deployments with Forgejo & Forgejo Actions has been another. Some playing around with automated semver releases for my Ansible roles too. Oh, and NixOS Flakes are experimental still but have great community support already.

Most of the learning and different parts have yet to come together into a place where I can have a minimal on-prem OS release that I can create/adjust for all the various bits of tech I want to learn or deploy in my homelab.

This very loose guide aims to document some of that learning into a place that I can easily look back on in the future to remember what/why I did what I did. Maybe it'll be helpful to you too? 🤞

Presumptions & Prerequisites

I'm making an assumption here that you've already got ...

  • An existing Proxmox Host to deploy the working LXCs into
  • An existing Forgejo instance you self host or have existing access to like on Codeberg.org
  • a existing familiarity with Platform/System administration like tasks like navigating the terminal/servers via cd, pwd, ssh, ls, git, scp, etc.

You can substitute out parts of the guide for other things, but my ability to help or provide guidance/clarification won't be as great.

Local Environment Setup

I'm all about testing and deploying things locally first. I'm also hopping between my Windows 10 Desktop and a borrowed M1 MacBook. Generically I spend more time in Linux/Unix like environments that I'll default to something like WSL in Windows than figure out PowerShell equivalent commands. Sorry ahead of time if you're hoping for a ton of PS1 scripts or snippets.

Nix Check / Install

The LXCs we'll be building are going to be built using NixOS's package manager, which is called nix , so if it's not already installed you can install it via the recommended method in the Nix (2.18) reference manual.

For WSL that looks like: bash <(curl -L https://nixos.org/nix/install) --daemon.

The nix command will be used for managing Flakes and when this was written Flakes are still experimental. You may want to create a shell alias in non-NixOS environments with something like

alias nix="nix --extra-experimental-features flakes --extra-experimental-features nix-command"

If you encounter errors such as

error: experimental Nix feature 'nix-command' is disabled; add '--extra-experimental-features nix-command' to enable it

Create a Git Repo

I'm storing my code in a self-hosted version of Forgejo (git.auengun.net) which uses a GitHub Actions like flavor of CI tooling, and has a similar interface to something like GitHub or GitLab.

Create a git repo locally or remotely and then make sure it's available in the same environment as the nix binary is installed from the previous check step.

Create the NixOS flake.nix

NixOS can be configured in a few ways, for this particular deployment method I'll be using an experimental feature which uses a flake.nix file. That file is eventually turned into a .tar.gz archive that we deploy into Proxmox.

Copy the following to a file named flake.nix in the Git repo created above.

{
  description = "NixOS Auto Upgrading Proxmox LXC";

  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.05";

    # "The nixos-generators project allows to take the same
    #    NixOS configuration, and generate outputs for
    #    different target formats."
    # https://github.com/nix-community/nixos-generators
    nixos-generators = {
      url = "github:nix-community/nixos-generators";
      inputs.nixpkgs.follows = "nixpkgs";
    };
  };

  outputs = { self, nixpkgs, nixos-generators, home-manager, ... }: {
    nixosConfigurations.base = nixpkgs.lib.nixosSystem {
      system = "x86_64-linux";

      modules = [

        # This module could be in an entire other file,
        # but for demonstration purposes it lives here.
        # https://nixos.wiki/wiki/NixOS_modules
        ({ pkgs, lib, ... }: {

          # Enable experimental features "nix-command" and "flakes"
          # https://search.nixos.org/options?channel=24.05&show=nix
          nix = {
            package = pkgs.nixFlakes;
            registry.nixpkgs.flake = nixpkgs;
            extraOptions = ''
              experimental-features = nix-command flakes
            '';
          };

          # This block instructs NixOS to autoUpgrade daily at 5:35am from the Flake
          # sourced from my own Forgejo Git instance. You can change this to your own
          # Forgejo or GitHub or GitLab or w/e if you need. Note the `#base` in the
          # `flake` variable below. That's the "name" of "this" particular system
          # since we can actually have more than 1 system or configuration in this file.
          # https://search.nixos.org/options?channel=24.05&show=system.autoUpgrade
          system.autoUpgrade = {
            enable = true;
            flake =
              "git+https://git.auengun.net/GregoryDosh/proxmox-lxc-nixos?ref=main#base";
            flags = [ "--update-input" "nixpkgs" "--no-write-lock-file" "-L" ];
            dates = "05:35";
          };

          # This doesn't really need to be updated except in certain circumstances.
          # It's okay for this stateVersion to not match the nixpkgs/nixos- version in `inputs` above.
          # https://search.nixos.org/options?channel=24.05&show=system.stateVersion
          system.stateVersion = "24.11";

          # Installs a few packages at the system level for demonstration purposes.
          # https://search.nixos.org/options?channel=24.05&show=environment.systemPackages
          environment.systemPackages = with pkgs; [ curl git gdu htop ];

          # Enables SSH Access to the LXC. See below for which SSH keys are allowed.
          # https://search.nixos.org/options?channel=24.05&show=services.openssh
          services.openssh = {
            enable = true;
            settings.PasswordAuthentication = false;
            settings.KbdInteractiveAuthentication = false;
            settings.PermitRootLogin = "yes";
          };

          # Change this to an SSH Public Key you control, not me ;-)
          # https://search.nixos.org/options?channel=24.05&show=users.extraUsers.<name>.openssh.authorizedKeys.keys
          users.extraUsers.root.openssh.authorizedKeys.keys = [
            "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMXkv/D0gs/1VCz7xyBEcb6zEaO4av3fwOzYEUsrIGVP"
          ];

          # This is the Root Certificate for my PKI.
          # You almost certainly don't need this, but for my private
          # infrastructure I use this to maintain HTTPS communication
          # between my various hosts.
          # https://en.wikipedia.org/wiki/Public_key_infrastructure
          # https://search.nixos.org/options?channel=24.05&show=security.pki.certificates
          security.pki.certificates = [''
            -----BEGIN CERTIFICATE-----
            MIIBvDCCAWGgAwIBAgIQEfOFqF449oxRcowJxyMitDAKBggqhkjOPQQDAjA8MRgw
            FgYDVQQKEw9TUE0gSW50ZXJuYWwgQ0ExIDAeBgNVBAMTF1NQTSBJbnRlcm5hbCBD
            QSBSb290IENBMB4XDTIzMTEyNzA1MDQzNFoXDTMzMTEyNDA1MDQzNFowPDEYMBYG
            A1UEChMPU1BNIEludGVybmFsIENBMSAwHgYDVQQDExdTUE0gSW50ZXJuYWwgQ0Eg
            Um9vdCBDQTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABFCC6vfeuVITTNm7H477
            8VaJjloSxdrkW5M8AcomcTIzY/JmnxTTytCSemAsq3noEn20VuH+8aNbDFeRurga
            MrGjRTBDMA4GA1UdDwEB/wQEAwIBBjASBgNVHRMBAf8ECDAGAQH/AgEBMB0GA1Ud
            DgQWBBRl+U21IKOJLCzPQVOcwGx3N/UqmDAKBggqhkjOPQQDAgNJADBGAiEAh367
            sTd4tmub0hFm4q9i6PDD1yyf2qHoQm7kQ6lVTOgCIQDmUk4QI+CRZUwZ9CA5tgEO
            SRUNfFs0UEzXBZD0uvMF6A==
            -----END CERTIFICATE-----
          ''];
        })

        # This enables *all* formats from the nixos-generators
        # instead of just the proxmox-lxc format. This is so I can
        # easily test changes locally with other formats like WSL native LXD/LXC
        # or Docker instead of the Proxmox LXC format.
        # https://github.com/nix-community/nixos-generators/?tab=readme-ov-file#supported-formats
        (nixos-generators.nixosModules.all-formats)
      ];
    };
  };
}

Building the NixOS LXC in a local environment

We can now build the LXC locally with the file above. Presuming things haven't drifted too much since this was written and how things generally operate with Nix.

# If you've not already added the flake.nix file to the git repo, do that now.
# NixOS will only see files that are *seen* by Git.
git add flake.nix

nix build .#nixosConfigurations.base.config.formats.proxmox-lxc -o ./proxmox-lxc.tar.xz

The resulting file will be a symlink to the location in the Nix store.

Add the flake.lock to the Git repo

If things build successfully the output of the nix build command above will be a new flake.lock file in your local repo too. This file is what allows for the reproducibility of builds into the future. It's also what we'll be updating in Forgejo/CI so that the live instance of this LXC can grab the most recent "changes" in the flake.nix and flake.lock files.

Also don't forget to push those changes to your remote Forgejo/Git repository! git push

Create a Forgejo (GitHub Actions Compliant) workflow to update the repo

Forgejo Actions supports a somewhat compliant GitHub Actions like interface but it won't be exact, so this following example should work closely with GitHub Actions too. Though this specifically is targeting my self-hosted Forgejo instance so I won't be able to help/support anything GitHub Actions related.

Create a file in the .forgejo/workflows (.github/workflows) location with whatever name you'd like (I picked Update NixOS Flake.yml, along with the following file content.

name: Update NixOS Flake

on:
  workflow_dispatch:
  schedule:
    - cron: '0 0 * * 0'

jobs:
  update_lockfile:
    runs-on: ubuntu-act-latest
    steps:
      - name: Checkout repository
        uses: https://github.com/actions/checkout@v4

      - name: Install Nix
        uses: https://github.com/DeterminateSystems/nix-installer-action@main

      - name: Update flake.lock
        uses: https://github.com/DeterminateSystems/update-flake-lock@main
        with:
          pr-title: "Update flake.lock"
          pr-labels: |
            dependencies

This allows for a manual workflow dispatch, along with an automated Cron schedule and uses some community Actions to accomplish the task of opening PRs with lockfile updates. Adjust to your own liking for workflows.

Example Auto Upgrade Run

Member Comments