Complete usage instructions and examples for the NixOS Infrastructure Control Operator.
- Quick Start
- Machine Management
- Configuration Management
- Kubernetes Cluster Management
- Advanced Features
- Troubleshooting
- Kubernetes cluster (v1.20+)
- Kubectl configured with cluster access
- Target machines accessible via SSH
- Git repository with NixOS configurations
# Apply Custom Resource Definitions
kubectl apply -f crds/
# Deploy the operator
kubectl apply -f deployment.yaml
# Verify operator is running
kubectl get pods -l app=nico-operator- Create SSH Key Secret
kubectl create secret generic machine-ssh-key \
--from-file=ssh-privatekey=~/.ssh/id_rsa \
--namespace=default- Create Machine Resource
kubectl apply -f examples/machine-example.yaml- Apply NixOS Configuration
kubectl apply -f examples/nixosconfiguration-example.yaml- Create Kubernetes Cluster (Optional)
kubectl apply -f examples/kubernetescluster-example.yamlThe Machine resource represents physical or virtual machines managed by the NIO (NixOS Infrastructure Operator). NICO uses Machine resources to provision Kubernetes cluster nodes.
For detailed Machine management documentation, see the NIO repository.
apiVersion: nio.homystack.com/v1alpha1
kind: Machine
metadata:
name: worker-01
labels:
role: worker # Used by NICO for machine selection
spec:
hostname: worker-01.local
ipAddress: 192.168.1.100
sshUser: root
sshKeySecretRef:
name: machine-ssh-keyThe NixosConfiguration resource defines NixOS configurations applied to machines. This is managed by the NIO (NixOS Infrastructure Operator). NICO automatically creates NixosConfiguration resources for cluster nodes.
For detailed NixosConfiguration documentation, see the NIO repository.
apiVersion: nio.homystack.com/v1alpha1
kind: NixosConfiguration
metadata:
name: worker-config
labels:
nico.homystack.com/cluster: "my-cluster" # Added by NICO
nico.homystack.com/role: "worker" # Added by NICO
spec:
gitRepo: "https://github.com/your-org/nixos-configs.git"
flake: ".#worker-01"
configurationSubdir: "nix"
fullInstall: false
machineRef:
name: worker-01
onRemoveFlake: "#minimal" # Set by NICO for cleanupNote: When using NICO for Kubernetes cluster management, you don't need to create NixosConfiguration resources manually - NICO creates them automatically based on your KubernetesCluster specification.
The KubernetesCluster resource manages complete Kubernetes clusters with control plane and worker nodes.
apiVersion: nico.homystack.com/v1alpha1
kind: KubernetesCluster
metadata:
name: production-cluster
namespace: default
spec:
gitRepo: "https://github.com/your-org/kubernetes-configs.git"
configurationSubdir: "clusters/production"
controlPlane:
machineSelector:
matchLabels:
role: control-plane
count: 3
dataPlane:
machineSelector:
matchLabels:
role: worker
count: 5
credentialsRef:
name: git-credentialsCheck cluster status:
kubectl get kubernetescluster production-cluster -o yamlExpected status:
status:
phase: "Ready" # Provisioning | ControlPlaneReady | Ready | Failed | Deleting
controlPlaneReady: "3/3"
dataPlaneReady: "5/5"
kubeconfigSecret: "production-cluster-kubeconfig"
appliedMachines:
cp-01: "production-cluster-cp-01"
cp-02: "production-cluster-cp-02"
worker-01: "production-cluster-worker-01"
conditions:
- type: "Ready"
status: "True"
lastTransitionTime: "2025-01-21T08:30:00Z"
reason: "AllNodesReady"
message: "Control plane: 3/3, Workers: 5/5"NICO tracks cluster lifecycle through the following phases:
- Provisioning: Initial state when NixosConfiguration resources are being created
- ControlPlaneReady: All control plane nodes are configured and ready
- Ready: All nodes (control plane + workers) are configured and ready
- Failed: Cluster provisioning encountered a permanent error
- Deleting: Cluster is being deleted
When the control plane reaches "Ready" state, NICO automatically creates a Secret containing the kubeconfig:
# Access kubeconfig
kubectl get secret production-cluster-kubeconfig -o jsonpath='{.data.kubeconfig}' | base64 -d > cluster-kubeconfig.yaml
# Use with kubectl
export KUBECONFIG=./cluster-kubeconfig.yaml
kubectl get nodesKubeconfig Extraction: NICO extracts kubeconfig from the first control plane node via SSH in a distribution-agnostic way:
-
Tries standard locations for different Kubernetes distributions:
/etc/rancher/k3s/k3s.yaml(k3s)/var/lib/k0s/pki/admin.conf(k0s)/etc/kubernetes/admin.conf(kubeadm)/root/.kube/config(generic)/etc/kubernetes/kubeconfig(generic)
-
Fallback to kubectl: If no file found, tries
kubectl config view --raw -
SSH Authentication: Uses the SSH key from
Machine.spec.sshKeySecretRef
Note: Kubeconfig extraction happens automatically when control plane becomes ready. Check operator logs if extraction fails.
Inject additional files into the configuration:
additionalFiles:
# From Kubernetes secret
- path: "secrets/api-key"
value:
secretRef:
name: api-secret
key: api-key
# Inline Nix configuration
- path: "config/network.nix"
value:
inline: |
{ config, pkgs, ... }:
{
networking.hostName = "server-01";
networking.firewall.enable = true;
}
# From NixOS facter
- path: "facts/system-info"
value:
nixosFacter: trueNICO implements automatic cascade deletion for cluster resources. When you delete a KubernetesCluster resource:
- All NixosConfiguration resources created by NICO are automatically deleted (via ownerReference)
- Machines revert to
hasConfiguration: falseand become available for reuse - Cleanup configurations are applied using
onRemoveFlake: "#minimal" - Secrets (join-token, kubeconfig) are cleaned up
# Delete cluster - automatically cleans up all related resources
kubectl delete kubernetescluster production-cluster
# Verify machines are released
kubectl get machines -l role=control-plane -o jsonpath='{.items[*].status.hasConfiguration}'
# Should return: false false falseImportant: The onRemoveFlake is set to #minimal by default, which should revert machines to a minimal NixOS configuration. Ensure your Git repository provides a #minimal flake output for cleanup.
The cleanup configuration is automatically set for cluster resources:
# Automatically added by NICO to each NixosConfiguration
spec:
onRemoveFlake: "#minimal" # Reverts to minimal config on deletionYou can customize the cleanup flake in your Git repository by providing a #minimal output in your flake.nix.
For private repositories, create a secret:
kubectl create secret generic git-credentials \
--from-literal=token=your-github-token \
--namespace=defaultNICO exposes comprehensive Prometheus metrics on port 8080 for monitoring operator health and cluster state.
# Port forward to metrics endpoint
kubectl port-forward -n nico-operator-system svc/nico-operator-metrics 8080:8080
# View metrics
curl http://localhost:8080/metricsCluster Metrics:
nico_clusters_total- Total number of KubernetesCluster resourcesnico_clusters_by_phase{namespace, phase}- Clusters grouped by phase (Provisioning, Ready, etc.)nico_cluster_control_plane_nodes{namespace, cluster, status}- Control plane node counts (ready/total)nico_cluster_worker_nodes{namespace, cluster, status}- Worker node counts (ready/total)
Operation Metrics:
nico_cluster_reconcile_duration_seconds{namespace, cluster}- Time spent reconciling clustersnico_cluster_reconcile_success_total{namespace, cluster}- Successful reconciliationsnico_cluster_reconcile_errors_total{namespace, cluster, error_type}- Failed reconciliations
Configuration Metrics:
nico_nixos_configs_created_total{namespace, cluster, role}- Created NixosConfigurationsnico_nixos_configs_deleted_total{namespace, cluster}- Deleted NixosConfigurationsnico_kubeconfig_generation_success_total{namespace, cluster}- Successful kubeconfig generations
Machine Selection Metrics:
nico_machine_selection_duration_seconds{namespace, cluster, role}- Time to select machinesnico_machines_selected{namespace, cluster, role}- Number of selected machines
If using prometheus-operator, the ServiceMonitor is automatically created:
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: nico-operator
namespace: nico-operator-system
spec:
selector:
matchLabels:
app: nico-operator
endpoints:
- port: metrics
interval: 30s# Clusters not in Ready state
nico_clusters_by_phase{phase!="Ready"}
# Average reconciliation time
rate(nico_cluster_reconcile_duration_seconds_sum[5m]) / rate(nico_cluster_reconcile_duration_seconds_count[5m])
# Reconciliation error rate
rate(nico_cluster_reconcile_errors_total[5m])
# Control plane readiness
nico_cluster_control_plane_nodes{status="ready"} / nico_cluster_control_plane_nodes{status="total"}
kubectl logs -l app=nico-operator -n nico-operator-system -fkubectl get events --field-selector involvedObject.kind=NixosConfiguration
kubectl get events --field-selector involvedObject.kind=KubernetesCluster# Test SSH connection manually
ssh -i ~/.ssh/id_rsa root@192.168.1.100 "hostname"# List all applied configurations
kubectl get machines -o custom-columns=NAME:.metadata.name,CONFIG:.status.appliedConfiguration,COMMIT:.status.appliedCommit
# Check specific machine
kubectl describe machine worker-01Symptoms:
- Machine status shows
discoverable: false - SSH connection failures
Solutions:
- Verify network connectivity
- Check SSH key configuration
- Ensure SSH service is running on target machine
- Verify SSH user permissions
Symptoms:
- Configuration status shows errors
- No commit hash recorded
Solutions:
- Check Git repository accessibility
- Verify flake path correctness
- Review operator logs for specific errors
- Check disk space on target machine
Symptoms:
- Cluster phase stuck in "Provisioning"
- Control plane nodes not ready
Solutions:
- Check machine resources (CPU, memory)
- Verify network connectivity between nodes
- Review Kubernetes component logs
- Check etcd cluster health
# Get detailed resource information
kubectl describe nixosconfiguration <name>
kubectl describe kubernetescluster <name>
# Check operator pod status
kubectl get pods -l app=nico-operator
# View operator logs with timestamps
kubectl logs -l app=nico-operator --tail=100 --timestamps
# Check events for specific resource
kubectl get events --field-selector involvedObject.name=<resource-name>- Use Git Tags: For production, use specific Git tags instead of branches
- Optimize Flakes: Keep flake configurations minimal and focused
- Monitor Resources: Ensure target machines have adequate resources
- Batch Operations: Apply configurations during maintenance windows
- Use dedicated SSH keys for operator access
- Store sensitive data in Kubernetes secrets
- Limit SSH user permissions to necessary commands
- Regularly rotate credentials
- Restrict access to kubeconfig Secrets
- Use Git tags for production configurations
- Implement health checks for critical services
- Monitor applied commit hashes via Prometheus metrics
- Test configurations in staging first
- Set up alerts for reconciliation errors
- Regularly update NixOS channels
- Monitor disk usage on target machines
- Keep operator and dependencies updated
- Maintain backup procedures
- Monitor metrics for anomalies
- Development: Make changes in feature branches
- Testing: Apply to test machines first
- Review: Create pull requests for changes
- Production: Merge to main and apply tags
- Monitoring: Watch applied commit status and Prometheus metrics
NICO v1alpha1 introduces several automatic behaviors that improve lifecycle management:
All NixosConfiguration resources created by NICO have an ownerReference pointing to their parent KubernetesCluster. This means:
- ✅ Automatic cleanup: Deleting a cluster automatically removes all its configurations
- ✅ No orphaned resources: Machines are automatically released for reuse
⚠️ Cannot delete configs independently: You cannot manually delete individual NixosConfiguration resources while the cluster exists
# This will delete the cluster AND all its NixosConfiguration resources
kubectl delete kubernetescluster my-cluster
# This will fail - configs are protected by ownerReference
kubectl delete nixosconfiguration my-cluster-cp-01
# Error: admission webhook denied (or will be recreated by controller)When the control plane becomes ready:
- ✅ Kubeconfig Secret is automatically created
- ✅ Named
<cluster-name>-kubeconfig - ✅ Distribution-agnostic extraction: Works with k3s, k0s, kubeadm, and other distributions
- ✅ SSH-based: Extracts via SSH from first control plane node
- ✅ Multiple fallbacks: Tries standard paths and kubectl command
# Secret appears automatically when phase=ControlPlaneReady
kubectl get secret my-cluster-kubeconfig
# Extract and use
kubectl get secret my-cluster-kubeconfig -o jsonpath='{.data.kubeconfig}' | base64 -d > kubeconfig.yaml
export KUBECONFIG=./kubeconfig.yaml
kubectl get nodesExtraction process:
- SSH to first control plane node using Machine's SSH key
- Try standard kubeconfig locations for different distributions
- Fallback to
kubectl config view --rawif files not found - Parse and validate YAML format
- Store in Secret for cluster access
NICO automatically sets cleanup behavior for all cluster machines:
- Uses
onRemoveFlake: "#minimal"for cleanup - Your Git repository must provide a
#minimalflake output - This reverts machines to a minimal state when cluster is deleted
Required in your flake.nix:
{
outputs = { self, nixpkgs }: {
# Your cluster node configurations
nixosConfigurations.cp-01 = ...;
nixosConfigurations.worker-01 = ...;
# REQUIRED: Minimal cleanup configuration
nixosConfigurations.minimal = nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
modules = [
({ config, pkgs, ... }: {
# Minimal system configuration
boot.loader.grub.enable = true;
fileSystems."/" = { device = "/dev/sda1"; fsType = "ext4"; };
networking.useDHCP = true;
services.openssh.enable = true;
})
];
};
};
}All NixosConfiguration resources get automatic labels:
metadata:
labels:
nico.homystack.com/cluster: "my-cluster"
nico.homystack.com/role: "control-plane" # or "worker"Use these labels for filtering and debugging:
# Find all configurations for a cluster
kubectl get nixosconfigurations -l nico.homystack.com/cluster=my-cluster
# Find all control plane configurations
kubectl get nixosconfigurations -l nico.homystack.com/role=control-planeCluster status is updated every 30 seconds with real-time node readiness:
# Watch cluster status in real-time
kubectl get kubernetescluster my-cluster -w
# Check detailed status
kubectl get kubernetescluster my-cluster -o jsonpath='{.status}' | jqIf upgrading from a previous version:
- Backup existing resources before upgrading
- Update Git repositories to include
#minimalflake output - Update monitoring to use new Prometheus metrics endpoint
- Review RBAC - operator now needs permissions for ownerReferences
- Test cascade deletion in non-production environment first
The examples/ directory contains:
machine-example.yaml- Basic machine definitionnixosconfiguration-example.yaml- Configuration applicationkubernetescluster-example.yaml- Complete cluster setup- Various NixOS configuration examples
- Review the Configuration Reference for detailed options
- Check the Development Guide for debugging and development
- Explore the Nix configurations in the nix/ directory