Beyond the golden image: an EUC veteran meets NixOS
Part 1 : Golden images aren't dead, but there's another world
As a solution architect in the digital workplace space, I have spent a good part of my career designing around golden images and app layers. Master VMs, platform layers, driver packs, the whole ritual of seal, snapshot, recompose and pray. And over the last years just as much the modern workplace with Workspace ONE and Intune: policies instead of images, Autopilot instead of task sequences. More than 20 years of doing things the Microsoft way.
So when I recently started playing with NixOS, my intitial take on it: "how hard can it be?"

My first real reaction was not "wow, how elegant". I think it was closer to: "I believe none of this, this is way too complex, I'm going to cry now."
Spoiler: not that much later I had a laptop that installs itself and builds its entire configuration from a Git repo. In this series I'll take you along, including the parts that didn't work.
Where it clicked, and where it didn't
The learning curve is real. My first attempt was putting the DAWO configuration (more on what DAWO is below) on top of a clean NixOS install, and it went nowhere fast. Too many moving parts, too many things I didn't understand yet. I literally gave up for the evening.
What helped me in the end was letting go of the image mindset. Stop thinking "what do I put on this machine" and start thinking "what does this machine declare". Once I had my own small repo with DAWO-Core underneath, things went a lot faster.
Why an EUC veteran looks at NixOS at all
One thing to be clear about upfront: this is not a VDI story. DAWO runs on physical laptops. In the world I know, that's Workspace ONE, Intune and Autopilot, and before that SCCM or MDT task sequences with a captured reference image. Familiar territory, just with a completely different approach.
Here's the thing that often trips people up. In our world, the image is the configuration. You build it once, you hope nobody touches it, and then you spend the rest of its life fighting drift. GPOs, Intune baselines, UEM compliance policies: a lot of our tooling is there to check if a device still looks like what we designed.
NixOS flips that around. There is no image to drift from. The configuration is a set of text files, and the system is built from those files, every single time. Change the file, rebuild, and that's it. Something wrong? The previous version is still sitting in your boot menu as a so-called generation. It feels like a snapshot revert, and that is exactly the trap I fell into. More on that below.
Why this matters: sovereignty, but not in black and white
First the big one: digital sovereignty. It's a hot topic, and for good reasons. When the operating system, the identity provider, the management plane and the productivity suite all come from the same handful of US vendors, your exit strategy is often nothing more than a slide in a presentation. An open Linux workplace that is completely defined in your own Git repo is a pretty concrete way to change that.
But hey, the world isn't black and white. Most organizations I know run on Entra ID today. Accounts, Conditional Access and MFA are all there. If you give users a second identity next to that, you don't get more sovereignty, you just get more work.
So I'm taking a pragmatic approach. A NixOS workplace that integrates with Entra ID where that is needed today, built in a way that the dependency becomes a choice instead of a lock-in. The open-source Himmelblau project is a good example of that idea. It handles Entra ID sign-in on Linux through PAM and NSS, can even do Intune enrollment and compliance (though think twice about sovereignty before you go there: that puts your device management back in the same cloud), and ships a NixOS module. And its installer also supports generic OIDC providers like Keycloak. So the same building block can work with a different identity provider.
For me that's the point: Entra is essential in this phase, but I want to be able to swap it later. In a declarative world, switching identity provider should be a change in one file in my repo, not a migration project. If that really works, I'll let you know later in this series.
So what is DAWO?
DAWO stands for Digitaal Autonome Werkomgeving Overheid, roughly the Digital Autonomous Work Environment for Government. It is an open-source, NixOS-based workplace initiative from the Dutch government, with the team linked to the Ministry of the Interior (BZK). It is not just a desktop either: the blueprint also covers office software, collaboration tools, cloud and IT management. Several Dutch municipalities are already piloting it in practice. The code now lives on Codeberg as DAWO-Core, with the original repo at code.overheid.nl as a backup.
This took me longer to understand than I'd like to admit: DAWO is not an image and not really a template either. It is a library of building blocks. A security baseline (SSH hardening, sysctl, USBGuard, audit), boot loader, hardware modules, desktop profiles, and comin for pulling updates from Git. You don't copy it, you build on top of it.
Fun fact: the current DAWO pilots run on older PCs that don't meet the Windows 11 hardware requirements (IT-Connect). Good news for the dusty laptops in the back of my hardware cabinet. And for another hot topic: our digital footprint. A laptop that keeps working for a few more years doesn't end up on the e-waste pile. Wait, are we solving two problems at once here? Awesome! More on my own dusty laptop in a minute.
Generations and flakes, in plain EUC language
My first mistake, having grown up in a virtualization world: I assumed a generation was a snapshot, and a flake was a layer, like an app layer or an OS layer. Both wrong. Once I understood why, the rest started to make sense.
Generations are NixOS's built-in versioning. Every time you rebuild the system with a new configuration, NixOS doesn't overwrite the old one. It creates a new generation next to it, and the older ones show up as entries in your boot menu. Broke something? Pick the previous entry at boot, or roll back from the command line. Old generations stay around until you clean them up with garbage collection. And this is where my snapshot analogy broke: a generation only covers the system itself, not your data. Roll back and your files in /home stay exactly as they are. Generations also aren't full copies; they share everything that didn't change.
The rebuild command decides how a new generation lands:
Command | What happens |
sudo nixos-rebuild test | Builds and activates the new config now, without a boot entry. Reboot and you're back on the previous one |
sudo nixos-rebuild boot | Adds a new generation to the boot menu, active after the next reboot |
sudo nixos-rebuild switch | Both: a new generation, active immediately |
sudo nixos-rebuild switch --rollback | Back to the previous generation |
Coming from the image world, test is the interesting one. It's basically try this in production, but with a free undo on reboot.
Flakes are how you package a Nix configuration so it can be shared and reproduced. A flake is a folder or Git repo with a flake.nix that declares two things: its inputs (where it gets its building blocks, like nixpkgs or DAWO-Core) and its outputs (what it produces, like the configuration for a specific laptop). Next to it sits flake.lock, which pins every input to an exact commit.
For me the lock file is the best part.
Think of it as a bill of materials for your entire workplace: build the same flake with the same lock file and you get the same result, today or six months from now. No more "but it worked on last quarter's reference image".
And no, a flake is not a layer. An app layer is a chunk of files that gets stacked onto a disk. A flake is a recipe: it describes what to build and where the ingredients come from, and Nix turns it into one complete system. Nothing gets stacked, everything gets merged into a single configuration. That's also why the layer comparison in the next section only goes so far.
Layers, but not the ones you know
If you have ever worked with App Layering or App Volumes, the word "layers" will feel familiar. It is also slightly misleading, so bear with me.
My own repo is tiny. It takes DAWO-Core as a flake input and only adds what is mine:
dawo-werkplek/
├── flake.nix picks the blocks, merges the layers
├── org/ layer 2: applies to ALL laptops
│ ├── default.nix
│ └── apps.nix my own building block (applications)
├── hosts/
│ └── laptop-sata.nix layer 3: just this hardware
└── install.sh zero-touch installer (part 2)The mapping to our world looks like this:
What we know from EUC | The DAWO / NixOS equivalent |
Golden image (master VM, or a captured reference image for laptops) | DAWO-Core as a flake input |
Platform layer, GPOs, Intune baseline | The org/ layer in my own repo |
Driver pack, hardware-specific tweaks | One file per host in hosts/ |
Recompose a new image, or push changes through SCCM or Intune | Update flake.lock, devices pull it via comin (the idea, comin is still off in my lab) |
Fighting configuration drift | Largely gone: the system is rebuilt from config (user data and app state aside). With dawo.autoUpdate on, comin checks the repo every 30 minutes by default and rebuilds when there are new commits |
Snapshot revert (VDI) or reimaging the laptop | Boot the previous generation |
And now the bit where the analogy breaks. In App Layering, layer order matters: the top layer wins. In Nix, there is no chain. flake.nix simply puts everything side by side in one list, and Nix merges it into a single configuration. Who wins is decided by the priority of a value, not by its position. And if two values have the same priority? Nix refuses to guess and throws an error. I wish GPOs did that.
A real example. DAWO's default disk layout assumes NVMe. My initial test laptop, an Old Yeller I grabbed from the back of my hardware cabinet, has a SATA disk. So my host file overrides exactly that one value, with lib.mkForce:
{ lib, ... }:
{
networking.hostName = "laptop-sata";
# DAWO's layout points to nvme0n1, this machine has sda
disko.devices.disk.main.device = lib.mkForce "/dev/sda";
dawo.desktop.plasma.enable = true;
dawo.secureboot.enable = false; # off while I'm testing
dawo.autoUpdate.enable = false; # off while I'm testing
}Eleven lines. That is the entire device-specific configuration for this machine.
One config for the whole organization, with room for exceptions
Eleven lines for one laptop, nice. But I don't design for one laptop, and neither do you. For this to be more than a hobby project, the configuration has to scale. I want to set things once for the entire organization, and still allow exceptions where needed. A department with a specific app, a model with a quirky disk, a group of users that needs something extra.
This is where those priorities come in handy. Nix has a few levels:
Strength | How you write it | Where I use it |
Weakest (priority 1000) | lib.mkDefault | Organization-wide defaults in org/ that anyone may override |
Normal (priority 100) | A plain assignment | Regular settings in org/ or a host file |
Strongest (priority 50) | lib.mkForce | Deliberate exceptions, like my SATA disk |
Yes, in Nix the lower number wins. Don't ask me why. What matters is the result: the organization sets the rules with sensible defaults, and an exception is one explicit line in the place where it applies. And you can see it in Git, with a commit message explaining why. Compare that to finding out which GPO, which Intune profile and which assignment filter ended up winning on one specific device.
Exceptions don't have to be per device either. A module per department or device type, imported by a group of hosts, works the same way. Same principle, no extra tooling.
Lessons from week one
A few things I'd tell anyone starting out:
Enable SSH, git and flakes first on a fresh install, before you start playing around. Three lines in configuration.nix save you a lot of typing on a laptop keyboard.
Reference DAWO, don't copy it. My repo doesn't contain a copy of DAWO-Core, it points to it as an input. Think of it as not touching the vendor's base layer and only maintaining your own layers on top.
Use --refresh after every commit when building from a remote flake, or Nix happily uses a cached older version and you will question your sanity.
Those three lines for the first tip, inside the braces of /etc/nixos/configuration.nix:
# SSH access (for testing only)
services.openssh.enable = true;
# Git, needed to fetch a flake repo
programs.git.enable = true;
# Flakes on by default
nix.settings.experimental-features = [ "nix-command" "flakes" ];Then apply it and find your IP address:
sudo nixos-rebuild switch
ip aThe architect in me has questions
Getting one laptop running is one thing. Designing this for an organization is something else, and that's the part I'm really here for. A few questions I'm asking myself, without answers yet:
Who owns the repo? Who is allowed to change org/, and who only touches a host file?
Does a pull request become the change process? Review, approval and a Git history as your audit trail sounds great on paper. Now make it work in a real organization.
How do you take Windows admins along? Going from GPOs and Intune profiles to Nix code is a big step, and tooling alone won't fix that.
The bar: Windows Autopilot
To be honest about my expectations: I think the Windows Autopilot experience for end users is fantastic, or Apple's version on macOS with Automated Device Enrollment. A new laptop goes from its factory state to ready-to-use, the user signs in with their work account, and IT never has to touch the device. That's what users expect nowadays.
My NixOS story today is still "boot from a USB stick and run a script". Zero-touch for me, not yet for the user. But the ingredients are there: a declarative config in Git, comin to pull changes, and Himmelblau to sign in with an existing Entra account. Can a NixOS workplace get close to that out-of-box experience? Only time will tell.
What's next
I haven't decided yet where this series goes next. There's plenty left: the installation itself and all the errors I ran into, TPM and Secure Boot, adding my own applications, and the Entra ID integration. Either way, I'll take you along.
Are you exploring declarative workplaces too, or are golden images still doing the job for you? For me it's both, depending on the use case. Let me know in the comments.



Opmerkingen