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:

  1. NixOS Flakes: The source of truth for all configurations.
  2. OneDev: A self-hosted git server and CI/CD platform that handles the triggers.
  3. 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!