Automated Nix Flake Updates

Published Dec 7, 2025
Revision: 1

Nix and its ecosystem has become one of my computing obsessions. This year I've built up a flake with outputs for many of my devices spanning Linux, MacOS, and Android. I find myself running nix flake update daily. Most of the time changes have no conflict, but breaking changes or build failures can slip under the radar if they conflict with my Android configuration while I update and build on Linux.

To remove my personal need to update the lockfile of my dotfiles I turned to GitHub actions. Actions can be run on multiple platforms for free on public projects. This allows me to verify that my flake builds under multiple hardware and software configurations.

Updating the flake

The first step for this workflow is updating my flake.lock file. This can be done manually with the nix flake update command, but for actions I found DeterminateSystems' purpose-built update-flake-lock action.

name: Update Flake

on:
  schedule:
    - cron: "0 0 * * *"
  workflow_dispatch:

jobs:
  update-flake:
    runs-on: ubuntu-latest
    permissions:
      contents: write
      pull-requests: write

    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Setup Nix
        uses: ./.github/actions/setup-nix

      - name: Update flake.lock
        uses: DeterminateSystems/update-flake-lock@v28
        with:
          token: ${{ secrets.GH_TOKEN_FOR_UPDATES }}
          pr-title: "chore: update flake.lock"
          pr-labels: automated
          pr-body: |
            Automated flake dependency update.

            {{env.GIT_COMMIT_MESSAGE}}

An important detail here is GH_TOKEN_FOR_UPDATES. The default author for automated pull requests is Github actions bot. When Github actions bot opens a Pull Request automated checks are not run. To get automated checks I've created a PAT (Personal Access Token) to open the pull request with my identity.

This action runs daily, opening a Pull Request labeled automated with the updated flake.lock.

In this pull request we can see that my inputs for ghostty, home manager, nixpkgs, and nixpkgs unstable were all updated.

Automated Merge

With the pull request open we can now take advantage of automated checks to gatekeep these changes. We only want to merge if the updated flake inputs can build without error on each targeted platform. To verify these builds I will use nix build with the --dry-run flag. This validates the configuration as if we were building it, but does not build all of the packages. Since all we are looking for is verification of the build here this is perfect.

jobs:
  check-x86-linux:
    name: Check x86_64-linux configs
    runs-on: ubuntu-latest
    strategy:
      matrix:
        config: [desktop, eink, pocket]

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Setup Nix
        uses: ./.github/actions/setup-nix

      - name: Build ${{ matrix.config }} configuration
        run:
          nix build .#nixosConfigurations.${{ matrix.config
          }}.config.system.build.toplevel --dry-run

I made a short action to setup nix. Once in the shell nix build can be run directly. This is all I need for each platform, this example is running on ubuntu-latest. Checking other systems is as easy as changing runs-on to macos-latest and ubuntu-24.04-arm.

check-darwin:
  name: Check aarch64-darwin config
  runs-on: macos-latest

  steps:
    - name: Checkout code
      uses: actions/checkout@v4

    - name: Setup Nix
      uses: ./.github/actions/setup-nix

    - name: Build macbook configuration
      run: nix build .#darwinConfigurations.macbook.system --dry-run

My complete pull request action can be viewed here. The last component to complete this automation workflow is an auto merge configuration. Here I use the automated label that was set by the update-flake-lock action to choose which PRs to auto merge.

auto-merge:
  needs: [check-x86-linux, check-arm-linux, check-darwin]
  runs-on: ubuntu-latest
  if: contains(github.event.pull_request.labels.*.name, 'automated')
  permissions:
    contents: write
    pull-requests: write

  steps:
    - name: Enable auto-merge
      run: gh pr merge --auto --squash "$PR_URL"
      env:
        PR_URL: ${{ github.event.pull_request.html_url }}
        GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

I set out to automate away my need to run nix flake update and this workflow has succeeded! Now if I think about updating I will run a git pull instead to see if changes came in last night. This has also been doubly useful for exposing when my configuration is broken for a different machine than the one I edited it from. I'll have to mute this notification eventually, but for now I enjoy receiving an email in the evening letting me know another daily update has passed successfully.