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
- and a Forgejo Actions Runner
- 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.