DevOps help for Cloud Platform Engineers
  • Welcome!
  • Quick Start Guide
  • About Me
  • CV
  • Contribute
  • 🧠DevOps & SRE Foundations
    • DevOps Overview
      • Engineering Fundamentals
      • Implementing DevOps Strategy
      • DevOps Readiness Assessment
      • Lifecycle Management
      • The 12 Factor App
      • Design for Self Healing
      • Incident Management Best Practices (2025)
    • SRE Fundamentals
      • Toil Reduction
      • System Simplicity
      • Real-world Scenarios
        • AWS VM Log Monitoring API
    • Agile Development
      • Team Agreements
        • Definition of Done
        • Definition of Ready
        • Team Manifesto
        • Working Agreement
    • Industry Scenarios
      • Finance and Banking
      • Public Sector (UK/EU)
      • Energy Sector Edge Computing
  • DevOps Practices
    • Platform Engineering
    • FinOps
    • Observability
      • Modern Practices
  • 🚀Modern DevOps Practices
    • Infrastructure Testing
    • Modern Development
    • Database DevOps
  • 🛠️Infrastructure as Code (IaC)
    • Terraform
      • Cloud Integrations - Provider-specific implementations
        • Azure Scenarios
          • Azure Authetication
            • Service Principal
            • Service Principal in block
            • Service Principal in env
        • AWS Scenarios
          • AWS Authentication
        • GCP Scenarios
          • GCP Authentication
      • Testing and Validation
        • Unit Testing
        • Integration Testing
        • End-to-End Testing
        • Terratest Guide
      • Best Practices
        • State Management
        • Security
        • Code Organization
        • Performance
      • Tools & Utilities - Enhancing the Terraform workflow
        • Terraform Docs
        • TFLint
        • Checkov
        • Terrascan
      • CI/CD Integration - Automating infrastructure deployment
        • GitHub Actions
        • Azure Pipelines
        • GitLab CI
    • Bicep
      • Getting Started - First steps with Bicep [BEGINNER]
      • Template Specs
      • Best Practices - Guidelines for effective Bicep implementations
      • Modules - Building reusable components [INTERMEDIATE]
      • Examples - Sample implementations for common scenarios
      • Advanced Features
      • CI/CD Integration - Automating Bicep deployments
        • GitHub Actions
        • Azure Pipelines
  • 💰Cost Management & FinOps
    • Cloud Cost Optimization
  • 🐳Containers & Orchestration
    • Containerization Overview
      • Docker
        • Dockerfile Best Practices
        • Docker Compose
      • Kubernetes
        • CLI Tools - Essential command-line utilities
          • Kubectl
          • Kubens
          • Kubectx
        • Core Concepts
        • Components
        • Best Practices
          • Pod Security
          • Security Monitoring
          • Resource Limits
        • Advanced Features - Beyond the basics [ADVANCED]
          • Service Mesh
            • Istio
            • Linkerd
          • Ingress Controllers
            • NGINX
            • Traefik
            • Kong
            • Gloo Edge
            • Contour
        • Tips
          • Status in Pods
          • Resource handling
          • Pod Troubleshooting Commands
        • Enterprise Architecture
        • Health Management
        • Security & Compliance
        • Virtual Clusters
      • OpenShift
  • Service Mesh & Networking
    • Service Mesh Implementation
  • Architecture Patterns
    • Data Mesh
    • Multi-Cloud Networking
    • Disaster Recovery
    • Chaos Engineering
  • Edge Computing
    • Implementation Guide
      • Serverless Edge
      • IoT Edge Patterns
      • Real-Time Processing
      • Edge AI/ML
      • Security Hardening
      • Observability Patterns
      • Network Optimization
      • Storage Patterns
  • 🔄CI/CD & GitOps
    • CI/CD Overview
      • Continuous Integration
      • Continuous Delivery
        • Deployment Strategies
        • Secrets Management
        • Blue-Green Deployments
        • Deployment Metrics
        • Progressive Delivery
        • Release Management for DevOps/SRE (2025)
      • CI/CD Platforms - Tool selection and implementation
        • Azure DevOps
          • Pipelines
            • Stages
            • Jobs
            • Steps
            • Templates - Reusable pipeline components
            • Extends
            • Service Connections - External service authentication
            • Best Practices for 2025
            • Agents and Runners
            • Third-Party Integrations
            • Azure DevOps CLI
          • Boards & Work Items
        • GitHub Actions
        • GitLab
          • GitLab Runner
          • Real-life scenarios
          • Installation guides
          • Pros and Cons
          • Comparison with alternatives
      • GitOps
        • Modern GitOps Practices
        • GitOps Patterns for Multi-Cloud (2025)
        • Flux
          • Overview
          • Progressive Delivery
          • Use GitOps with Flux, GitHub and AKS
  • Source Control
    • Source Control Overview
      • Git Branching Strategies
      • Component Versioning
      • Kubernetes Manifest Versioning
      • GitLab
      • Creating a Fork
      • Naming Branches
      • Pull Requests
      • Integrating LLMs into Source Control Workflows
  • ☁️Cloud Platforms
    • Cloud Strategy
      • AWS to Azure
      • Azure to AWS
      • GCP to Azure
      • AWS to GCP
      • GCP to AWS
    • Azure
      • Best Practices
        • Azure Best Practices Overview
        • Azure Architecture Best Practices
        • Azure Naming Standards
        • Azure Tags
        • Azure Security Best Practices
      • Landing Zones
      • Services
        • Azure Active Directory (AAD)
        • Azure Monitor
        • Azure Key Vault
        • Azure Service Bus
        • Azure DNS
        • Azure App Service
        • Azure Batch
        • Azure Machine Learning
        • Azure OpenAI Service
        • Azure Cognitive Services
        • Azure Kubernetes Service (AKS)
        • Azure Databricks
        • Azure SQL Database
      • Monitoring
      • Administration Tools - Platform management interfaces
        • Azure PowerShell
        • Azure CLI
      • Tips & Tricks
    • AWS
      • Authentication
      • Best Practices
      • Tips & Tricks
      • Services
        • AWS IAM (Identity and Access Management)
        • Amazon CloudWatch
        • Amazon SNS (Simple Notification Service)
        • Amazon SQS (Simple Queue Service)
        • Amazon Route 53
        • AWS Elastic Beanstalk
        • AWS Batch
        • Amazon SageMaker
        • Amazon Bedrock
        • Amazon Comprehend
    • Google Cloud
      • Services
        • Cloud CDN
        • Cloud DNS
        • Cloud Load Balancing
        • Google Kubernetes Engine (GKE)
        • Cloud Run
        • Artifact Registry
        • Compute Engine
        • Cloud Functions
        • App Engine
        • Cloud Storage
        • Persistent Disk
        • Filestore
        • Cloud SQL
        • Cloud Spanner
        • Firestore
        • Bigtable
        • BigQuery
        • VPC (Virtual Private Cloud)
  • 🔐Security & Compliance
    • DevSecOps Overview
      • DevSecOps Pipeline Security
      • DevSecOps
        • Real-life Examples
        • Scanning & Protection - Automated security tooling
          • Dependency Scanning
          • Credential Scanning
          • Container Security Scanning
          • Static Code Analysis
            • Best Practices
            • Tool Integration Guide
            • Pipeline Configuration
        • CI/CD Security
        • Secrets Rotation
      • Supply Chain Security
        • SLSA Framework
        • Binary Authorization
        • Artifact Signing
      • Security Best Practices
        • Threat Modeling
        • Kubernetes Security
      • SecOps
      • Zero Trust Model
      • Cloud Compliance
        • ISO/IEC 27001:2022
        • ISO 22301:2019
        • PCI DSS
        • CSA STAR
      • Security Frameworks
      • SIEM and SOAR
  • Security Architecture
    • Zero Trust Implementation
      • Identity Management
      • Network Security
      • Access Control
  • 🔍Observability & Monitoring
    • Observability Fundamentals
      • Logging
      • Metrics
      • Tracing
      • Dashboards
      • SLOs and SLAs
      • Observability as Code
      • Pipeline Observability
  • 🧪Testing Strategies
    • Testing Overview
      • Modern Testing Approaches
      • End-to-End Testing
      • Unit Testing
      • Performance Testing
        • Load Testing
      • Fault Injection Testing
      • Integration Testing
      • Smoke Testing
  • 🤖AI Integration
    • AIops Overview
      • Workflow Automation
      • Predictive Analytics
      • Code Quality
  • 🧠AI & LLM Integration
    • Overview
      • Claude
        • Installation Guide
        • Project Guides
        • MCP Server Setup
        • LLM Comparison
      • Ollama
        • Installation Guide
        • Configuration
        • Models and Fine-tuning
        • DevOps Usage
        • Docker Setup
        • GPU Setup
        • Open WebUI
      • Copilot
        • Installation Guide
        • VS Code Integration
        • CLI Usage
      • Gemini
        • Installation Guides - Platform-specific setup
          • Linux Installation
          • WSL Installation
          • NixOS Installation
        • Gemini 2.5 Features
        • Roles and Agents
        • NotebookML Guide
        • Cloud Infrastructure Deployment
        • Summary
  • 💻Development Environment
    • DevOps Tools
      • Operating Systems - Development platforms
        • NixOS
          • Install NixOS: PC, Mac, WSL
          • Nix Language Deep Dive
          • Nix Language Fundamentals
            • Nix Functions and Techniques
            • Building Packages with Nix
            • NixOS Configuration Patterns
            • Flakes: The Future of Nix
          • NixOS Generators: Azure & QEMU
        • WSL2
          • Distributions
          • Terminal Setup
      • Editor Environments
      • CLI Tools
        • Azure CLI
        • PowerShell
        • Linux Commands
          • SSH - Secure Shell)
            • SSH Config
            • SSH Port Forwarding
        • Linux Fundametals
        • Cloud init
          • Cloud init examples
        • YAML Tools
          • How to create a k8s yaml file - How to create YAML config
          • YQ the tool
  • 📚Programming Languages
    • Python
    • Go
    • JavaScript/TypeScript
    • Java
    • Rust
  • Platform Engineering
    • Implementation Guide
  • FinOps
    • Implementation Guide
  • AIOps
    • LLMOps Guide
  • Should Learn
    • Should Learn
    • Linux
      • Commands
      • OS
      • Services
    • Terraform
    • Getting Started - Installation and initial setup [BEGINNER]
    • Cloud Integrations
    • Testing and Validation - Ensuring infrastructure quality
      • Unit Testing
      • Integration Testing
      • End-to-End Testing
      • Terratest Guide
    • Best Practices - Production-ready implementation strategies
      • State Management
      • Security
      • Code Organization
      • Performance
    • Tools & Utilities
    • CI/CD Integration
    • Bicep
    • Kubernetes
      • kubectl
    • Ansible
    • Puppet
    • Java
    • Rust
    • Azure CLI
  • 📖Documentation Best Practices
    • Documentation Strategy
      • Project Documentation
      • Release Notes
      • Static Sites
      • Documentation Templates
      • Real-World Examples
  • 📋Reference Materials
    • Glossary
    • Tool Comparison
    • Tool Decision Guides
    • Recommended Reading
    • Troubleshooting Guide
    • Development Setup
Powered by GitBook
On this page
  • What Are Flakes?
  • Basic Flake Structure
  • Flake Inputs and Outputs
  • Inputs
  • Outputs
  • Using Flakes in DevOps Workflows
  • Development Environment Flake
  • CI/CD Pipeline Flake
  • Microservices Flake
  • NixOS System Configuration with Flakes
  • Advanced Flake Techniques
  • Multi-Environment Deployments
  • Composing Flakes with Registry Overrides
  • Managing Flake Dependencies
  • Dependency Locking and Updates
  • Dependency Visualization
  • Flakes in CI/CD Pipelines
  • Real-World DevOps Projects with Flakes
  • NixOS Server Fleet Management
  • Best Practices for Flakes in Production
  • Flakes Command Reference
  • Conclusion
  • Further Resources
Edit on GitHub
  1. Development Environment
  2. DevOps Tools
  3. Operating Systems - Development platforms
  4. NixOS
  5. Nix Language Fundamentals

Flakes: The Future of Nix

Nix Flakes represent the next evolution in Nix package management, providing a more structured, reproducible approach to managing dependencies. This guide explores how to use Flakes effectively in DevOps workflows.

What Are Flakes?

Flakes are a Nix feature that provides a standardized approach to:

  1. Dependency Management: Lock exact versions of dependencies

  2. Composable Configuration: Create modular, reusable configurations

  3. Reproducible Builds: Guarantee identical environments every time

  4. Self-Contained Projects: Define complete development environments

A Flake is defined by a flake.nix file and a corresponding flake.lock file that pins exact dependency versions.

Basic Flake Structure

A minimal flake.nix file looks like this:

{
  description = "My project flake";

  inputs = {
    # Core dependencies
    nixpkgs.url = "github:NixOS/nixpkgs/nixos-23.11";
    
    # Additional dependencies
    flake-utils.url = "github:numtide/flake-utils";
  };

  outputs = { self, nixpkgs, flake-utils }: 
    flake-utils.lib.eachDefaultSystem (system:
      let
        pkgs = nixpkgs.legacyPackages.${system};
      in {
        # Development environment
        devShells.default = pkgs.mkShell {
          buildInputs = with pkgs; [
            nodejs_20
            yarn
          ];
        };
        
        # Packages
        packages = {
          default = self.packages.${system}.myapp;
          
          myapp = pkgs.stdenv.mkDerivation {
            name = "myapp";
            version = "1.0.0";
            src = ./src;
            
            buildPhase = ''
              # Build commands
            '';
            
            installPhase = ''
              mkdir -p $out/bin
              cp myapp $out/bin/
            '';
          };
        };
        
        # Apps (runnable packages)
        apps.default = {
          type = "app";
          program = "${self.packages.${system}.myapp}/bin/myapp";
        };
      }
    );
}

Flake Inputs and Outputs

Inputs

The inputs section defines external dependencies:

inputs = {
  # From GitHub
  nixpkgs.url = "github:NixOS/nixpkgs/nixos-23.11";
  
  # From a specific commit
  nixpkgs-unstable.url = "github:NixOS/nixpkgs/5233fd2ba76a3accb05b4104bd6e6dda4061a396";
  
  # From local path
  utils = {
    url = "path:./nix-utils";
    inputs.nixpkgs.follows = "nixpkgs"; # Ensure consistent nixpkgs
  };
  
  # With flake = false for non-flake repos
  legacy-project = {
    url = "github:example/legacy-project";
    flake = false;
  };
}

Outputs

The outputs section defines what the flake provides:

outputs = { self, nixpkgs, ... }:
  let
    # Systems to support
    systems = [ "x86_64-linux" "aarch64-linux" "x86_64-darwin" "aarch64-darwin" ];
    
    # Helper to generate per-system outputs
    forAllSystems = nixpkgs.lib.genAttrs systems;
    
    # Package set for each system
    pkgsFor = system: import nixpkgs { inherit system; };
  in {
    # NixOS configurations
    nixosConfigurations = {
      myserver = nixpkgs.lib.nixosSystem {
        system = "x86_64-linux";
        modules = [ ./nixos/configuration.nix ];
      };
    };
    
    # Per-system outputs
    packages = forAllSystems (system: {
      default = self.packages.${system}.myapp;
      myapp = (pkgsFor system).callPackage ./pkgs/myapp.nix {};
    });
    
    # Development shells
    devShells = forAllSystems (system: {
      default = (pkgsFor system).mkShell {
        buildInputs = with (pkgsFor system); [ go gopls ];
      };
    });
    
    # Formatter for nix files
    formatter = forAllSystems (system: (pkgsFor system).nixpkgs-fmt);
  };

Using Flakes in DevOps Workflows

Development Environment Flake

Create consistent development environments across your team:

{
  description = "Development environment for our microservices";

  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixos-23.11";
    flake-utils.url = "github:numtide/flake-utils";
  };

  outputs = { self, nixpkgs, flake-utils }:
    flake-utils.lib.eachDefaultSystem (system:
      let
        pkgs = nixpkgs.legacyPackages.${system};
        
        # Define project-specific variables
        projectName = "acme-services";
        
        # Define common dependencies
        commonDeps = with pkgs; [
          docker-compose
          kubectl
          kubernetes-helm
          jq
          yq
          git
        ];
        
        # Define language-specific dependencies
        goDeps = with pkgs; [ go gopls golangci-lint ];
        nodeDeps = with pkgs; [ nodejs_20 yarn nodePackages.typescript ];
        pythonDeps = with pkgs; [
          (python311.withPackages (ps: with ps; [ 
            pytest 
            requests 
            pyyaml
            black
            mypy
          ]))
        ];
        
      in {
        # Define multiple development shells
        devShells = {
          default = pkgs.mkShell {
            name = "${projectName}-default";
            buildInputs = commonDeps;
            
            shellHook = ''
              echo "${projectName} development environment"
              echo "Run 'nix develop .#backend' for Go development"
              echo "Run 'nix develop .#frontend' for Node.js development"
              echo "Run 'nix develop .#scripts' for Python scripts development"
            '';
          };
          
          # Backend (Go) development shell
          backend = pkgs.mkShell {
            name = "${projectName}-backend";
            buildInputs = commonDeps ++ goDeps;
            
            shellHook = ''
              echo "Backend (Go) development environment ready!"
              export GOPATH=$PWD/.go
              export PATH=$GOPATH/bin:$PATH
              mkdir -p $GOPATH/bin
            '';
          };
          
          # Frontend (Node.js) development shell
          frontend = pkgs.mkShell {
            name = "${projectName}-frontend";
            buildInputs = commonDeps ++ nodeDeps;
            
            shellHook = ''
              echo "Frontend (Node.js) development environment ready!"
              export PATH=$PWD/node_modules/.bin:$PATH
              
              # Set up local node_modules bin directory
              mkdir -p ./.npm-global/bin
              export NPM_CONFIG_PREFIX=$PWD/.npm-global
              export PATH=$NPM_CONFIG_PREFIX/bin:$PATH
              
              # Disable update notifier to keep environment clean
              export NO_UPDATE_NOTIFIER=1
            '';
          };
          
          # Scripts (Python) development shell
          scripts = pkgs.mkShell {
            name = "${projectName}-scripts";
            buildInputs = commonDeps ++ pythonDeps;
            
            shellHook = ''
              echo "Scripts (Python) development environment ready!"
              
              # Create and activate a virtual environment if it doesn't exist
              if [ ! -d .venv ]; then
                echo "Creating Python virtual environment..."
                python -m venv .venv
              fi
              
              source .venv/bin/activate
            '';
          };
        };
      }
    );
}

CI/CD Pipeline Flake

Define CI/CD configurations in a Flake:

{
  description = "CI/CD pipeline configurations";

  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixos-23.11";
    flake-utils.url = "github:numtide/flake-utils";
  };

  outputs = { self, nixpkgs, flake-utils }:
    flake-utils.lib.eachDefaultSystem (system:
      let
        pkgs = nixpkgs.legacyPackages.${system};
        
        # Define shared CI dependencies
        ciDeps = with pkgs; [
          docker
          git
          jq
          curl
          gnumake
          openssh
        ];
        
        # Create a common CI environment
        mkCiEnv = extraPkgs: pkgs.buildEnv {
          name = "ci-environment";
          paths = ciDeps ++ extraPkgs;
        };
        
      in {
        # CI environments for different jobs
        packages = {
          default = self.packages.${system}.build-env;
          
          # Environment for building code
          build-env = mkCiEnv (with pkgs; [ 
            nodejs_20
            yarn
            typescript
          ]);
          
          # Environment for testing
          test-env = mkCiEnv (with pkgs; [
            nodejs_20
            yarn
            chromium
            xvfb-run
          ]);
          
          # Environment for deployment
          deploy-env = mkCiEnv (with pkgs; [
            kubectl
            kubernetes-helm
            awscli2
            terraform
            sops
          ]);
        };
        
        # CI/CD scripts as apps
        apps = {
          default = self.apps.${system}.ci-build;
          
          ci-build = {
            type = "app";
            program = toString (pkgs.writeShellScript "ci-build" ''
              set -euo pipefail
              
              echo "Building application..."
              cd $GITHUB_WORKSPACE
              yarn install --frozen-lockfile
              yarn build
              
              echo "Running tests..."
              yarn test
              
              echo "Build successful!"
            '');
          };
          
          ci-deploy = {
            type = "app";
            program = toString (pkgs.writeShellScript "ci-deploy" ''
              set -euo pipefail
              
              echo "Deploying to Kubernetes..."
              
              # Configure kubectl
              echo "$KUBE_CONFIG" | base64 -d > kubeconfig.yaml
              export KUBECONFIG="$PWD/kubeconfig.yaml"
              
              # Deploy with Helm
              helm upgrade --install myapp ./helm/myapp \
                --namespace $DEPLOY_NAMESPACE \
                --set image.tag=$GITHUB_SHA \
                --set environment=$DEPLOY_ENV \
                --wait
              
              echo "Deployment successful!"
            '');
          };
        };
      }
    );
}

Microservices Flake

Manage multiple services in a single repository:

{
  description = "Microservices architecture";

  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixos-23.11";
    flake-utils.url = "github:numtide/flake-utils";
    
    # Additional tools
    crane = {
      url = "github:ipetkov/crane/master";
      inputs.nixpkgs.follows = "nixpkgs";
    };
  };

  outputs = { self, nixpkgs, flake-utils, crane, ... }:
    flake-utils.lib.eachDefaultSystem (system:
      let
        pkgs = nixpkgs.legacyPackages.${system};
        craneLib = crane.lib.${system};
        
        # Common dependencies for all services
        commonDeps = with pkgs; [
          docker-compose
          openssl
        ];
        
        # Build a Docker image for a service
        buildDockerImage = { name, dir, port ? 8080 }: 
          pkgs.dockerTools.buildImage {
            inherit name;
            tag = "latest";
            
            copyToRoot = pkgs.buildEnv {
              name = "image-root";
              paths = [ 
                self.packages.${system}.${name}
                pkgs.cacert
                pkgs.tzdata
              ];
              pathsToLink = [ "/bin" "/etc" "/tmp" ];
            };
            
            config = {
              Cmd = [ "/bin/${name}" ];
              ExposedPorts = {
                "${toString port}/tcp" = {};
              };
              Env = [
                "SSL_CERT_FILE=${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt"
                "TZ=UTC"
              ];
              WorkingDir = "/app";
              Volumes = {
                "/data" = {};
              };
            };
          };
        
        # Helper for Go services
        buildGoService = { name, dir, port ? 8080 }: {
          # Build the Go binary
          ${name} = craneLib.buildPackage {
            pname = name;
            version = "1.0.0";
            src = ./${dir};
            
            buildInputs = with pkgs; [ go_1_20 ];
          };
          
          # Build the Docker image
          "${name}-image" = buildDockerImage {
            inherit name dir port;
          };
        };
        
        # Helper for Node.js services
        buildNodeService = { name, dir, port ? 3000 }: {
          # Build the Node.js package
          ${name} = pkgs.buildNpmPackage {
            pname = name;
            version = "1.0.0";
            src = ./${dir};
            
            npmDepsHash = "sha256-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
            
            installPhase = ''
              mkdir -p $out/bin
              cp -r dist $out/dist
              cp package.json $out/
              cat > $out/bin/${name} << EOF
              #!/bin/sh
              exec ${pkgs.nodejs_20}/bin/node $out/dist/index.js "\$@"
              EOF
              chmod +x $out/bin/${name}
            '';
          };
          
          # Build the Docker image
          "${name}-image" = buildDockerImage {
            inherit name dir port;
          };
        };
        
        # Generate Terraform provider configurations
        terraformConfig = pkgs.writeTextFile {
          name = "terraform-providers.tf";
          text = ''
            terraform {
              required_providers {
                aws = {
                  source  = "hashicorp/aws"
                  version = "~> 5.0"
                }
                kubernetes = {
                  source  = "hashicorp/kubernetes"
                  version = "~> 2.0"
                }
              }
              required_version = ">= 1.0"
            }
            
            provider "aws" {
              region = var.aws_region
            }
            
            provider "kubernetes" {
              config_path = var.kubeconfig_path
            }
          '';
        };
        
        # Define services
        services = {
          auth = { name = "auth-service"; dir = "services/auth"; port = 8081; };
          api = { name = "api-gateway"; dir = "services/api"; port = 8080; };
          users = { name = "user-service"; dir = "services/users"; port = 8082; };
          frontend = { name = "frontend"; dir = "services/frontend"; port = 3000; };
        };
        
      in {
        # Packages for all services
        packages = {
          default = self.packages.${system}.all;
          
          # Meta-package for building all services
          all = pkgs.runCommandNoCC "all-services" {} ''
            mkdir -p $out
            echo "All services built successfully" > $out/success
          '';
          
          # Terraform configuration
          terraform = terraformConfig;
          
          # Build individual Go services
          auth-service = (buildGoService services.auth).auth-service;
          api-gateway = (buildGoService services.api).api-gateway;
          user-service = (buildGoService services.users).user-service;
          
          # Build individual Node.js services
          frontend = (buildNodeService services.frontend).frontend;
          
          # Docker images
          auth-service-image = (buildGoService services.auth)."auth-service-image";
          api-gateway-image = (buildGoService services.api)."api-gateway-image";
          user-service-image = (buildGoService services.users)."user-service-image";
          frontend-image = (buildNodeService services.frontend)."frontend-image";
        };
        
        # Development environments
        devShells = {
          default = pkgs.mkShell {
            buildInputs = with pkgs; [
              go_1_20
              nodejs_20
              yarn
              docker-compose
              kubectl
              terraform
            ];
          };
        };
        
        # Apps for development
        apps = {
          # Development launcher
          dev = {
            type = "app";
            program = toString (pkgs.writeShellScript "launch-dev" ''
              ${pkgs.docker-compose}/bin/docker-compose -f docker-compose.dev.yaml up
            '');
          };
          
          # Service runner
          auth = {
            type = "app";
            program = "${self.packages.${system}.auth-service}/bin/auth-service";
          };
          
          api = {
            type = "app";
            program = "${self.packages.${system}.api-gateway}/bin/api-gateway";
          };
          
          users = {
            type = "app";
            program = "${self.packages.${system}.user-service}/bin/user-service";
          };
        };
      }
    );
}

NixOS System Configuration with Flakes

Define your entire NixOS system in a flake:

{
  description = "My NixOS configurations";

  inputs = {
    # Core dependencies
    nixpkgs.url = "github:NixOS/nixpkgs/nixos-23.11";
    nixpkgs-unstable.url = "github:NixOS/nixpkgs/nixos-unstable";
    
    # System management
    home-manager = {
      url = "github:nix-community/home-manager/release-23.11";
      inputs.nixpkgs.follows = "nixpkgs";
    };
    
    # Hardware detection
    hardware.url = "github:NixOS/nixos-hardware";
    
    # Secrets management
    agenix = {
      url = "github:ryantm/agenix";
      inputs.nixpkgs.follows = "nixpkgs";
    };
  };

  outputs = { self, nixpkgs, nixpkgs-unstable, home-manager, hardware, agenix, ... }:
    let
      lib = nixpkgs.lib;
      
      # System types to support
      systems = [ "x86_64-linux" "aarch64-linux" ];
      forAllSystems = lib.genAttrs systems;
      
      # Nixpkgs configuration
      nixpkgsConfig = {
        allowUnfree = true;
        permittedInsecurePackages = [ "openssl-1.1.1u" ];
      };
      
      # Helper for creating system configurations
      mkSystem = { system, hostname, username ? "admin", extraModules ? [] }:
        lib.nixosSystem {
          inherit system;
          
          modules = [
            # Base configuration
            ./nixos/configuration.nix
            
            # Machine-specific configuration
            ./nixos/hosts/${hostname}
            
            # Hardware configuration
            ./nixos/hardware/${hostname}.nix
            
            # User configuration via home-manager
            home-manager.nixosModules.home-manager
            {
              home-manager.useGlobalPkgs = true;
              home-manager.useUserPackages = true;
              home-manager.users.${username} = import ./home/${username}.nix;
            }
            
            # Secrets management
            agenix.nixosModules.default
            
            # Hostname configuration
            {
              networking.hostName = hostname;
              nixpkgs.config = nixpkgsConfig;
              
              # Make flake inputs accessible in NixOS configuration
              _module.args = {
                flake-self = self;
                inputs = {
                  nixpkgs-unstable = nixpkgs-unstable;
                  agenix = agenix;
                  hardware = hardware;
                };
              };
            }
          ] ++ extraModules;
        };
        
      # Define overlay for accessing unstable packages
      overlays = {
        default = final: prev: {
          unstable = import nixpkgs-unstable {
            system = prev.system;
            config = nixpkgsConfig;
          };
        };
      };
      
    in {
      # NixOS system configurations
      nixosConfigurations = {
        # Desktop system
        desktop = mkSystem {
          system = "x86_64-linux";
          hostname = "desktop";
          username = "developer";
          extraModules = [
            # Use desktop hardware profile
            hardware.nixosModules.dell-xps-15-9500
            
            # Desktop-specific modules
            ./nixos/modules/desktop.nix
            ./nixos/modules/gaming.nix
          ];
        };
        
        # Server configuration
        server = mkSystem {
          system = "x86_64-linux";
          hostname = "server";
          extraModules = [
            ./nixos/modules/server.nix
            ./nixos/modules/monitoring.nix
            ./nixos/modules/services/web.nix
            ./nixos/modules/services/database.nix
          ];
        };
        
        # Raspberry Pi configuration
        rpi4 = mkSystem {
          system = "aarch64-linux";
          hostname = "rpi4";
          extraModules = [
            hardware.nixosModules.raspberry-pi-4
            ./nixos/modules/iot-gateway.nix
          ];
        };
      };
      
      # Overlays
      overlays = overlays;
      
      # Development shells available on all platforms
      devShells = forAllSystems (system: 
        let
          pkgs = nixpkgs.legacyPackages.${system};
        in {
          default = pkgs.mkShell {
            buildInputs = with pkgs; [
              nixpkgs-fmt
              nil
              nixos-rebuild
            ];
          };
        }
      );
      
      # Formatter for all platforms
      formatter = forAllSystems (system: nixpkgs.legacyPackages.${system}.nixpkgs-fmt);
    };
}

Advanced Flake Techniques

Multi-Environment Deployments

Define different environments (dev, staging, production) with shared configurations:

{
  description = "Multi-environment deployment flake";

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

  outputs = { self, nixpkgs, ... }:
    let
      lib = nixpkgs.lib;
      
      # Define supported systems
      supportedSystems = [ "x86_64-linux" "aarch64-linux" ];
      
      # Helper to create per-system attributes
      forAllSystems = lib.genAttrs supportedSystems;
      
      # Helper to create full system configurations
      mkSystem = { system, hostname, environment, region ? "us-east-1" }:
        let
          # Base nixosSystem configuration
          pkgs = import nixpkgs {
            inherit system;
            config.allowUnfree = true;
          };
          
          # Environment-specific settings
          envSettings = {
            dev = {
              logLevel = "debug";
              monitoring = true;
              highAvailability = false;
              instanceType = "t3.medium";
              domain = "dev.example.com";
              replicas = 1;
            };
            
            staging = {
              logLevel = "info";
              monitoring = true;
              highAvailability = true;
              instanceType = "t3.large";
              domain = "staging.example.com";
              replicas = 2;
            };
            
            prod = {
              logLevel = "warn";
              monitoring = true;
              highAvailability = true;
              instanceType = "m5.large";
              domain = "example.com";
              replicas = 3;
            };
          };
          
          # Get environment settings with defaults
          env = envSettings.${environment};
          
        in lib.nixosSystem {
          inherit system;
          
          modules = [
            # Base configuration that applies to all instances
            ./modules/base.nix
            
            # Role-specific configuration
            ./modules/roles/${hostname}.nix
            
            # Environment-specific configuration
            ./modules/environments/${environment}.nix
            
            # System configuration
            {
              networking.hostName = "${hostname}-${environment}";
              
              # Pass environment settings to the configuration
              _module.args = {
                inherit env;
                inherit environment;
                inherit region;
                
                # Create a unique name for the instance
                instanceName = "${hostname}-${environment}-${region}";
              };
              
              # Define environment variables for the system
              environment.variables = {
                ENV = environment;
                LOG_LEVEL = env.logLevel;
                DOMAIN = env.domain;
              };
            }
          ];
        };
        
      # Define all the hosts we need to create
      allHosts = [
        { hostname = "api"; role = "application"; }
        { hostname = "worker"; role = "worker"; }
        { hostname = "db"; role = "database"; }
      ];
      
      # Define all environments
      allEnvironments = [ "dev" "staging" "prod" ];
      
      # Define all regions (for multi-region deployments)
      allRegions = {
        dev = [ "us-east-1" ];
        staging = [ "us-east-1" ];
        prod = [ "us-east-1" "eu-west-1" ];
      };
      
      # Generate all possible combinations of hosts/environments/regions
      mkSystemConfigurations = system:
        lib.listToAttrs (
          lib.flatten (
            builtins.map (environment:
              builtins.map (region:
                builtins.map (host:
                  lib.nameValuePair
                    # Generate a unique name for this configuration
                    "${host.hostname}-${environment}-${region}"
                    # Create the system configuration
                    (mkSystem {
                      inherit system environment region;
                      hostname = host.hostname;
                    })
                ) allHosts
              ) (allRegions.${environment} or [ "us-east-1" ])
            ) allEnvironments
          )
        );
        
      # Generate deployment scripts for each environment
      mkDeployScript = environment: system:
        let
          pkgs = nixpkgs.legacyPackages.${system};
          # Get list of systems for this environment
          systems = builtins.filter (name: builtins.match ".*-${environment}-.*" name != null)
                    (builtins.attrNames self.nixosConfigurations);
        in
          pkgs.writeShellScriptBin "deploy-${environment}" ''
            set -e
            echo "Deploying ${environment} environment..."
            
            # Deploy all systems for this environment
            ${lib.concatMapStringsSep "\n" (system: ''
              echo "Deploying ${system}..."
              nixos-rebuild switch --flake .#${system} --target-host ${system}.${environment}.example.com
            '') systems}
            
            echo "Deployment complete!"
          '';
          
    in {
      # Define all NixOS system configurations
      nixosConfigurations = 
        (mkSystemConfigurations "x86_64-linux") //
        (mkSystemConfigurations "aarch64-linux");
      
      # Provide deployment scripts for each environment and system
      packages = forAllSystems (system: {
        default = self.packages.${system}."deploy-dev";
        
        # Create deployment scripts for each environment
        "deploy-dev" = mkDeployScript "dev" system;
        "deploy-staging" = mkDeployScript "staging" system;
        "deploy-prod" = mkDeployScript "prod" system;
      });
      
      # Provide formatters and checks
      formatter = forAllSystems (system: nixpkgs.legacyPackages.${system}.nixpkgs-fmt);
      
      # Simple checks to validate configurations
      checks = forAllSystems (system:
        let pkgs = nixpkgs.legacyPackages.${system};
        in {
          # Check Nix formatting
          format = pkgs.runCommand "check-format" {} ''
            ${pkgs.nixpkgs-fmt}/bin/nixpkgs-fmt --check ${./.}
            touch $out
          '';
        }
      );
    };
}

Composing Flakes with Registry Overrides

Compose multiple flakes with registry overrides to create a unified system:

{
  description = "Composite flake with registry overrides";

  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixos-23.11";
    
    # Additional flakes with own nixpkgs inputs
    monitoring-flake = {
      url = "github:my-org/monitoring-flake";
      # Override its nixpkgs to use ours
      inputs.nixpkgs.follows = "nixpkgs";
    };
    
    kubernetes-flake = {
      url = "github:my-org/kubernetes-flake";
      inputs.nixpkgs.follows = "nixpkgs";
    };
    
    database-flake = {
      url = "github:my-org/database-flake";
      inputs.nixpkgs.follows = "nixpkgs";
    };
  };

  outputs = { self, nixpkgs, monitoring-flake, kubernetes-flake, database-flake, ... }:
    let
      # Systems to support
      supportedSystems = [ "x86_64-linux" "aarch64-linux" ];
      forAllSystems = nixpkgs.lib.genAttrs supportedSystems;
      
      # Helper to create system configurations
      mkSystem = { system, modules ? [] }:
        nixpkgs.lib.nixosSystem {
          inherit system modules;
        };
        
    in {
      # Registry overrides
      nixosConfigurations = {
        # Composite system that combines all flakes
        full-system = mkSystem {
          system = "x86_64-linux";
          modules = [
            # Base configuration
            ./modules/configuration.nix
            
            # Import modules from other flakes
            monitoring-flake.nixosModules.prometheus
            monitoring-flake.nixosModules.grafana
            kubernetes-flake.nixosModules.kubernetes
            database-flake.nixosModules.postgresql
            
            # Configure with settings specific to our deployment
            {
              services.prometheus = {
                enable = true;
                scrapeConfigs = [
                  {
                    job_name = "node";
                    static_configs = [{
                      targets = [ "localhost:9100" ];
                    }];
                  }
                ];
              };
              
              services.kubernetes = {
                roles = [ "master" "node" ];
                addons.dashboard.enable = true;
              };
              
              services.postgresql = {
                enable = true;
                package = nixpkgs.legacyPackages.x86_64-linux.postgresql_14;
              };
            }
          ];
        };
      };
      
      # Make modules from this flake available to others
      nixosModules = {
        default = ./modules/default.nix;
        customService = ./modules/custom-service.nix;
      };
      
      # Expose packages from all flakes
      packages = forAllSystems (system: {
        default = self.packages.${system}.combined-tools;
        
        # Combine tools from all flakes into one meta-package
        combined-tools = nixpkgs.legacyPackages.${system}.buildEnv {
          name = "combined-tools";
          paths = [
            # Local packages
            (nixpkgs.legacyPackages.${system}.callPackage ./pkgs/local-tool {})
            
            # Packages from other flakes
            monitoring-flake.packages.${system}.prometheus-toolbox
            kubernetes-flake.packages.${system}.k8s-tools
            database-flake.packages.${system}.db-migration-tool
          ];
        };
      });
      
      # Expose apps from all flakes
      apps = forAllSystems (system: {
        default = self.apps.${system}.manage;
        
        # Our own apps
        manage = {
          type = "app";
          program = toString (nixpkgs.legacyPackages.${system}.writeShellScript "manage" ''
            echo "Management interface for combined system"
            echo "1. Monitoring tools"
            echo "2. Kubernetes tools"
            echo "3. Database tools"
            read -p "Select an option: " option
            
            case $option in
              1) ${monitoring-flake.apps.${system}.monitor.program} ;;
              2) ${kubernetes-flake.apps.${system}.kubectl-wrapper.program} ;;
              3) ${database-flake.apps.${system}.db-cli.program} ;;
              *) echo "Invalid option" ;;
            esac
          '');
        };
        
        # Re-export apps from other flakes
        monitor = monitoring-flake.apps.${system}.monitor;
        kubectl = kubernetes-flake.apps.${system}.kubectl-wrapper;
        db-cli = database-flake.apps.${system}.db-cli;
      });
    };
}

Managing Flake Dependencies

Dependency Locking and Updates

Flakes use a flake.lock file to pin exact dependencies:

# Create or update the lock file based on flake.nix
nix flake update

# Update a specific input
nix flake update nixpkgs

# Update with specific commits
nix flake lock --override-input nixpkgs github:NixOS/nixpkgs/5233fd2ba76a3accb05b4104bd6e6dda4061a396

Dependency Visualization

Visualize your flake dependencies:

nix flake show
nix flake metadata --json | jq

Flakes in CI/CD Pipelines

Configure CI/CD pipelines to use flakes:

# .github/workflows/build.yml
name: Build and Test

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      
      - name: Install Nix
        uses: cachix/install-nix-action@v20
        with:
          nix_path: nixpkgs=channel:nixos-23.11
          extra_nix_config: |
            experimental-features = nix-command flakes
      
      - name: Set up Cachix
        uses: cachix/cachix-action@v12
        with:
          name: my-project
          authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
      
      - name: Build application
        run: nix build .#app
      
      - name: Run tests
        run: nix flake check
      
      # Deployment step for main branch only
      - name: Deploy
        if: github.ref == 'refs/heads/main'
        run: nix run .#deploy

Real-World DevOps Projects with Flakes

NixOS Server Fleet Management

Example of a flake for managing a fleet of servers:

{
  description = "Server Fleet Management";

  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixos-23.11";
    
    # For secrets management
    agenix = {
      url = "github:ryantm/agenix";
      inputs.nixpkgs.follows = "nixpkgs";
    };
    
    # Deploy-rs for multi-target deployments
    deploy-rs = {
      url = "github:serokell/deploy-rs";
      inputs.nixpkgs.follows = "nixpkgs";
    };
  };

  outputs = { self, nixpkgs, agenix, deploy-rs, ... }:
    let
      # Helper for creating server configurations
      mkServer = { name, ip, system ? "x86_64-linux", roles ? [], region ? "us-east-1" }:
        let
          # Standard NixOS configuration
          nixosConfig = nixpkgs.lib.nixosSystem {
            inherit system;
            
            modules = [
              # Base configuration for all servers
              ./modules/base.nix
              
              # Role-specific configurations
              (nixpkgs.lib.mkIf (builtins.elem "web" roles) ./modules/roles/web.nix)
              (nixpkgs.lib.mkIf (builtins.elem "db" roles) ./modules/roles/database.nix)
              (nixpkgs.lib.mkIf (builtins.elem "cache" roles) ./modules/roles/cache.nix)
              (nixpkgs.lib.mkIf (builtins.elem "monitoring" roles) ./modules/roles/monitoring.nix)
              
              # Region-specific configuration
              ./modules/regions/${region}.nix
              
              # Load secrets management
              agenix.nixosModules.default
              
              # Server-specific configuration
              {
                networking = {
                  hostName = name;
                  inherit ip;
                  firewall.enable = true;
                };
                
                # Make some contextual variables available to all modules
                _module.args = {
                  serverName = name;
                  serverRoles = roles;
                  serverRegion = region;
                };
              }
            ];
          };
          
        in {
          # NixOS configuration
          config = nixosConfig;
          
          # Deploy-rs node configuration
          deploy.node = {
            hostname = ip;
            profiles.system = {
              user = "root";
              path = deploy-rs.lib.${system}.activate.nixos nixosConfig;
            };
          };
        };
        
      # Define all servers in the fleet
      servers = {
        # Web servers
        web-1 = mkServer {
          name = "web-1";
          ip = "10.0.1.11";
          roles = [ "web" ];
        };
        
        web-2 = mkServer {
          name = "web-2";
          ip = "10.0.1.12";
          roles = [ "web" ];
        };
        
        # Database servers
        db-1 = mkServer {
          name = "db-1";
          ip = "10.0.2.11";
          roles = [ "db" ];
        };
        
        db-2 = mkServer {
          name = "db-2";
          ip = "10.0.2.12";
          roles = [ "db" ];
        };
        
        # Cache server
        cache-1 = mkServer {
          name = "cache-1";
          ip = "10.0.3.11";
          roles = [ "cache" ];
        };
        
        # Monitoring server
        monitor = mkServer {
          name = "monitor";
          ip = "10.0.4.11";
          roles = [ "monitoring" ];
        };
        
        # European region server
        eu-web-1 = mkServer {
          name = "eu-web-1";
          ip = "10.1.1.11";
          roles = [ "web" ];
          region = "eu-west-1";
        };
      };
      
    in {
      # Export NixOS configurations
      nixosConfigurations = nixpkgs.lib.mapAttrs 
        (name: server: server.config) 
        servers;
      
      # Export deploy-rs node configurations
      deploy.nodes = nixpkgs.lib.mapAttrs 
        (name: server: server.deploy.node) 
        servers;
      
      # Add some checks to ensure the deployments are valid
      checks = builtins.mapAttrs
        (system: deploy-rs.lib.${system}.deployChecks self.deploy)
        { x86_64-linux = {}; aarch64-linux = {}; };
      
      # Helper apps for administration
      apps.x86_64-linux = {
        # Deploy all servers
        deploy-all = {
          type = "app";
          program = toString (nixpkgs.legacyPackages.x86_64-linux.writeShellScript "deploy-all" ''
            ${deploy-rs.defaultPackage.x86_64-linux}/bin/deploy .
          '');
        };
        
        # Deploy only web servers
        deploy-web = {
          type = "app";
          program = toString (nixpkgs.legacyPackages.x86_64-linux.writeShellScript "deploy-web" ''
            ${deploy-rs.defaultPackage.x86_64-linux}/bin/deploy .#web-1
            ${deploy-rs.defaultPackage.x86_64-linux}/bin/deploy .#web-2
            ${deploy-rs.defaultPackage.x86_64-linux}/bin/deploy .#eu-web-1
          '');
        };
        
        # Deploy only database servers
        deploy-db = {
          type = "app";
          program = toString (nixpkgs.legacyPackages.x86_64-linux.writeShellScript "deploy-db" ''
            ${deploy-rs.defaultPackage.x86_64-linux}/bin/deploy .#db-1
            ${deploy-rs.defaultPackage.x86_64-linux}/bin/deploy .#db-2
          '');
        };
      };
    };
}

Best Practices for Flakes in Production

  1. Pin Dependencies: Always commit your flake.lock and update dependencies deliberately.

  2. Modularize Configurations: Use the module system to break down complex configurations.

  3. Layered Architecture: Structure flakes with clear layering:

    • Base system configuration

    • Role-specific modules (web server, database, etc.)

    • Environment-specific modules (dev, staging, prod)

    • Host-specific overrides

  4. Test Before Deployment: Use nix flake check and write tests for your configurations.

  5. Use CI/CD Pipelines: Integrate flake-based builds into your CI/CD pipelines.

  6. Document Inputs and Outputs: Add good descriptions and documentation to your flakes.

  7. Use Flake Registry: Consider registering frequently used flakes in the global registry.

  8. Cache Aggressively: Use binary caches (like Cachix) to speed up builds and deployments.

  9. Prefer Small, Focused Flakes: Create separate flakes for different concerns and compose them.

  10. Version Your Flakes: Tag releases for important configurations to enable rollbacks.

Flakes Command Reference

# Build a flake output
nix build .#<output>

# Run a flake app
nix run .#<app>

# Develop with a flake shell
nix develop .#<devShell>

# Check a flake
nix flake check

# Show flake outputs
nix flake show

# Update flake lock file
nix flake update

# Lock a specific input
nix flake lock --override-input <input> <value>

Conclusion

Nix Flakes represent a significant improvement in Nix's dependency management and reproducibility story. As a DevOps engineer, Flakes provide the tools needed to:

  • Create truly reproducible development environments

  • Manage complex system configurations

  • Deploy consistent infrastructure across environments

  • Maintain a fleet of machines with confidence

While Flakes are still evolving, they have rapidly become the preferred approach for serious Nix users. By embracing Flakes, you can leverage Nix's full power with a more structured, composable approach.

Further Resources

PreviousNixOS Configuration PatternsNextNixOS Generators: Azure & QEMU

Last updated 4 days ago

💻
Nix Flakes: Official Documentation
Nix Flake Tutorial
Nixpkgs Flake Examples
Flake Community Templates
Flakes Book