Why Nix for business systems
What Nix and NixOS give a small or mid-size business running Frappe, Odoo or WordPress, how Avunu applies them, and when not to use them.
On this page
This document explains what Nix and NixOS buy a business that runs its own Frappe, Odoo or WordPress systems, how Avunu uses them, and where they are the wrong tool. Read it before you decide whether to adopt the Avunu Nix projects, then follow the how-to links at the end.
What Nix gives a business
Nix is a package manager and build system. NixOS is a Linux distribution configured entirely through Nix. For a business system, four properties matter more than the rest.
Reproducible builds
A traditional server is a pile of history: packages installed by hand, a virtualenv someone rebuilt last spring, a yarn install that resolved differently on Tuesday. Nobody can say exactly what is running.
With Nix, the application is built from locked inputs. In frappe-nix and odoo-nix the Python environment comes from uv.lock. In frappe-nix the JavaScript dependencies come from each app's own yarn.lock. In all three projects the system packages come from a pinned flake.lock. The same inputs produce the same result on a laptop, in CI and on the server.
Frappe goes one step further. The production package (builtBench) runs bench build at build time, so compiled assets are part of the artifact instead of something created on the server after deployment.
Configuration you can read and review
On NixOS the server is described by files in git. A change to the database, the web server or the number of workers is a commit that someone can review, and the history answers "who changed this and why" without detective work.
Here is the shape of an Odoo deployment, taken from the odoo-nix documentation:
services.odoo-nix = {
enable = true;
package = projectFlake.packages.x86_64-linux.default;
dbName = "acme";
database.createLocally = true;
adminPasswordFile = "/run/secrets/odoo-admin";
workers = 4;
nginx = { enable = true; domain = "erp.example.com"; };
};Secrets stay out of this file and out of the Nix store. The modules read passwords from files at start-up and write the final configuration with restrictive permissions. For secrets that travel with a project, frappe-nix supports age-encrypted files committed next to the code, and a check-secrets command that verifies the ciphertext is encrypted to the people you think it is.
Rollbacks
Every NixOS change produces a new system generation, and the old one stays on disk until you clean it up. If a deploy goes badly, you step back:
nixos-rebuild switch --rollbackRollback covers code and configuration. It does not cover your data, so the Avunu modules add a database safety net around migrations:
frappe-nix takes a
mysqldumpsnapshot before runningbench migrateon deploy. If the migration fails, it restores the snapshot and leaves the site in maintenance mode.odoo-nix runs
odoo db migratebefore every start of Odoo, snapshots first, and rolls the database back on failure. A failed migration leaves Odoo stopped on purpose, because new code running against an old schema fails in ways nothing reports.
No lock-in
A Nix configuration is text that describes how to build and run the system. It does not depend on a particular vendor's control panel. The same projects produce:
a NixOS module for running directly on a NixOS host;
OCI container images, for hosts that run containers instead.
If you leave Avunu, you take the repository with you. If your hardware vendor changes, you rebuild the same configuration somewhere else.
How Avunu applies it
Avunu maintains one Nix project per platform. All three are consumed as flake-parts modules, so a client project is a short wrapper flake.nix plus the lock files, not a hand-built monolith.
| Project | Platform | Scaffold or adopt with | Production path |
|---|---|---|---|
| frappe-nix | Frappe and ERPNext benches, or a single Frappe app | nix run github:Avunu/frappe-nix | services.frappe NixOS module, OCI images |
| odoo-nix | Odoo built on OCB (Odoo Community Backports) and OCA modules | nix run github:Avunu/odoo-nix | services.odoo-nix NixOS module, OCI image |
| wordpress-nix | WordPress on FrankenPHP | nix run github:Avunu/wordpress-nix | services.wordpress-nix NixOS module, OCI images |
What the three share
A development shell that matches production. Each project provides a devenv shell that starts the application and its local services, so a new developer runs
direnv allowanddevenv upinstead of following a setup wiki.Mail that cannot escape. The development shells send all outgoing mail to a local Mailpit catcher. Frappe also ships "guard rails" that block backup uploads, outbound webhooks and other integrations on a bench restored from production data. The frappe-nix documentation is explicit that this reduces blast radius and is not an air gap.
One logging contract. Every service logs to the journal and carries
APP_SERVICEandAPP_SITEfields, so a single query can span a Frappe site, an Odoo database and a WordPress site.Matching module shape. Each project ships a NixOS module (
services.frappe,services.odoo-nix,services.wordpress-nix) that writes its configuration from Nix options. The Frappe and Odoo modules also run migrations automatically when the build changes, behind a snapshot, as described above.
Frappe
frappe-nix replaces bench init and bench get-app with a scaffolder that records apps as git submodules inside a uv workspace. It can also convert an existing bench in place, and it supports an app mode where a single app's repository carries only a small flake.nix and the bench around it is generated. The how-to is Frappe Development Environment with frappe-nix.
Odoo
odoo-nix scaffolds an Odoo project on OCB, optionally with curated OCA module bundles. It derives addons_path from the folders that are actually present and renders odoo.conf from Nix options, so neither can drift from the repository. A single odoo command handles database provisioning, migration, backup and restore. The how-to is Odoo Development Environment with odoo-nix.
WordPress
wordpress-nix runs WordPress on FrankenPHP and supports PHP 8.2 to 8.5 per deployment. On NixOS a site is either state-managed (a mutable WordPress core, managed from wp-admin) or git-managed (a read-only document root from a flake input, with plugin and theme installs from the UI disabled). It can adopt an existing WordPress install in place without moving or deleting anything. The how-to is WordPress on FrankenPHP with wordpress-nix.
Putting it on servers
Avunu hosts these systems on NixOS machines that consume the same packages the developers build. For the way the pieces fit together, read Reference Architecture for Self-Hosting Business Systems.
Honest trade-offs
Nix is powerful and it is not free. Weigh these before committing.
There is a learning curve. You need to read the Nix language at least well enough to follow a module, and you need flakes enabled. Error messages can be long and point at the wrong layer.
Lock files are a new chore. In Frappe, moving an app to a commit that adds a dependency makes
uv.lockstale, and the bench then fails to evaluate until you re-lock. frappe-nix names the problem and providesnix run .#relock, but you still have to do it.Imperative habits break. On a read-only Nix store, upstream commands that install packages into the environment do not work. frappe-nix wraps
bench update,bench get-appandbench restorefor this reason, so the flags follow frappe-nix's scripts rather than stock bench.Data is still state. Nix reproduces code and configuration, not your database or uploads. You still need backups and you still need to rehearse restoring them. Creating a Frappe site on a deployed host remains an operational step.
Platform limits apply. The production NixOS modules need a NixOS host. odoo-nix's own checks that run Odoo or a virtual machine are Linux-only. If your host is not NixOS, use the OCI images instead.
Git-managed WordPress changes who edits what. With a read-only document root, the site owner cannot install a plugin from wp-admin. That is the point, but you must agree on the workflow first.
When not to use Nix
Skip it, or postpone it, in these cases:
Nobody will own it. A reproducible system that no one on your side or ours understands is just a different black box. If you do not have a developer or a support partner, a managed host is a better fit.
The site is small and static. A brochure WordPress site with a handful of plugins does not need a declarative build pipeline.
You want to configure everything through the web UI. Production Nix deployments reward changing the repository, not clicking through an admin screen.
You need a platform the modules do not cover. The Avunu projects target Frappe, Odoo on OCB and OCA, and WordPress on FrankenPHP. Other stacks mean writing your own modules.
You are in a rush. Moving an existing bench or install onto Nix is a reconcile step, not a rewrite, but it still takes planning and a test restore.
Tip
You do not have to adopt everything at once. A good first step is the development shell alone: it gives every developer the same environment, and nothing about it requires changing how production runs.
Where to go next
Pick your platform and follow its development how-to: Frappe, Odoo or WordPress.
When you are ready to deploy, read Running Frappe as a NixOS Service, Running Odoo as a NixOS Service or the deployment sections of the WordPress how-to. Prefer containers? See Building Production Images with frappe-nix.
Read Reference Architecture for Self-Hosting Business Systems when you are ready to plan production hosting.
Nix is not only for servers. Two Avunu projects use it for devices: Building a Web Kiosk USB with web_kiosk and A NixOS Micro Desktop for Family and Friends. Mounting Cloud Storage with nixos-rclone covers declarative cloud storage mounts and sync.
Sources
This article is in the public domain (CC0 1.0), code samples included. Use it however helps you.