Overview
Managing a fleet of NixOS
machines can quickly become a manual chore if you’re still running nixos-rebuild switch from your local machine. In my homelab, I’ve moved towards a “hands-off” infrastructure where every change committed to the main branch of my monorepo is automatically built and deployed.
This post covers how I use NixOS Flakes
in platform/nixos, OneDev
for CI/CD, and Colmena
for the final deployment “push”.
The Infrastructure Stack
My setup revolves around three main components:
- NixOS Flakes: The source of truth for all configurations.
- OneDev: A self-hosted git server and CI/CD platform that handles the triggers.
- Colmena: A build and deployment tool for NixOS that allows for parallel deployments and remote builds.
Implementation
1. The NixOS Flake Configuration
The heart of the setup is the flake.nix located in platform/nixos. It defines the hosts and how they should be deployed. Here is a minimal but complete example showing two hosts:
{
description = "Homelab Platform Deployment Flake";
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
colmena.url = "github:nix-community/colmena";
};
outputs = { self, nixpkgs, colmena, ... }: {
colmena = {
meta = {
nixpkgs = import nixpkgs { system = "x86_64-linux"; };
};
monitoring = {
deployment = {
targetHost = "192.168.1.238";
targetUser = "root";
};
imports = [
./hosts/monitoring/configuration.nix
./common/default.nix
];
};
ingress = {
deployment = {
targetHost = "192.168.1.239";
targetUser = "root";
};
imports = [
./hosts/ingress/configuration.nix
./common/default.nix
];
};
};
};
}
2. OneDev Automation Workflow
OneDev is configured to watch for changes in the platform/nixos directory. When a commit hits main, it triggers a specific job for the affected host.
Here is a snippet from the .onedev-buildspec.yml:
- name: Update Nixos - Monitoring
steps:
- type: CheckoutStep
name: Checkout
- type: CommandStep
name: Push To LXC
runInContainer: true
image: nixos/nix:latest
interpreter:
type: DefaultInterpreter
commands: |
# Configure Nix for Flakes and Sandbox-less building
mkdir -p /etc/nix
cat >> /etc/nix/nix.conf <<EOF
sandbox = false
extra-experimental-features = nix-command flakes
EOF
# Setup SSH Key for Colmena
mkdir -p ~/.ssh
echo "$SSH_PRIVATE_KEY" > ~/.ssh/id_ed25519
chmod 600 ~/.ssh/id_ed25519
# Deploy with Colmena
cd platform/nixos
nix run nixpkgs#colmena -- apply --on monitoring
envVars:
- name: SSH_PRIVATE_KEY
value: "@secret:SSH_PRIVATE_KEY@"
triggers:
- type: BranchUpdateTrigger
branches: main
paths: platform/nixos/hosts/monitoring/** platform/nixos/common/default.nix
3. Automatic Updates: The Power of Path-Based Triggers
The BranchUpdateTrigger is the secret sauce for managing a monorepo efficiently. By using specific paths, I ensure that changes to the monitoring configuration only trigger a redeploy for that specific host.
In a monorepo containing everything from application code to different infrastructure tiers, this is a massive win. Without path-based triggers, every minor change to a random app would force a full CI/CD run for all NixOS hosts. This precision allows the homelab to scale without becoming a bottleneck of unnecessary build cycles.
Challenges & Solutions
The Nix Store Cache
Building NixOS configurations in a clean container every time can be slow. To solve this, I use OneDev’s SetupCacheStep to persist the /nix directory across builds. This significantly speeds up subsequent deployments as most dependencies are already present in the cache.
SSH Security
Since OneDev needs to log in to the target NixOS machines, I use a dedicated SSH private key stored as a secret in OneDev. The target machines have the corresponding public key added to the root user’s authorized_keys. (Note: For production setups, consider using a dedicated deployment user with sudo privileges instead of logging in as root directly).
Conclusion
By combining NixOS, Colmena, and OneDev, I’ve achieved a seamless GitOps workflow for my homelab. This isn’t just about avoiding manual updates; it’s about shifting to a fully declarative, auditable infrastructure.
Every change to my monitoring stack or ingress rules is now a pull request away from reality. This is just the first step in a broader move toward a GitOps-managed homelab, and in future posts, I’ll dive into how this same philosophy extends to my NixOS devbox and the containerized applications running on top of this platform.
Next steps? Integrating automated testing with nixos-test before the final deployment push!