NixOS's Declarative Model
NixOS is a Linux distribution that defines package management and system configuration within a single Nix expression. Packages defined in nixpkgs, their dependency trees, and build options are described via Nix expressions; these expressions are stored deterministically in the Nix store. System services, network settings, user accounts, and filesystem layout are declared in configuration.nix; when the file changes, the nixos-rebuild command can reevaluate the entire system.
This approach embraces the principle that configuration is always a data source. The same configuration.nix produces identical results on two different machines, forming the core of the Infrastructure as Code (IaC) philosophy. NixOS also tracks the exact version and configuration of every package, thereby providing technical reproducibility.
Creating a Basic Configuration File
The following configuration.nix example defines basic SSH access, an nginx service, and a postgresql database. After placing the file at /etc/nixos/configuration.nix, run the nixos-rebuild command.
{ config, pkgs, ... }:
{
imports = [ <nixpkgs/nixos/modules/installer/scan/not-detected.nix> ];
boot.loader.grub.device = "/dev/sda";
networking.hostName = "demo-server";
services.openssh.enable = true;
services.nginx = {
enable = true;
virtualHosts."example.com" = {
root = "/var/www/example";
};
};
services.postgresql = {
enable = true;
package = pkgs.postgresql_14;
dataDir = "/var/lib/postgresql/14/data";
authentication = {
enable = true;
users = [
{ name = "admin"; password = "securePassword"; superuser = true; }
];
};
};
}
After saving this configuration file, run the following command:
sudo nixos-rebuild switch
The command compiles the packages and brings the services up as defined. The output shows the building and activating phases; an example line:
building Nix store path... done
activating configuration... done
Warning: The
nixos-rebuild switchcommand modifies the current system configuration. In production environments, it is recommended to verify changes in a test environment before applying them.
Managing Services: PostgreSQL and Nginx
After the declarative definition, the actual state of the services is inspected with traditional systemctl commands. The systemctl output shows when the service was activated and the location of its unit file, allowing you to verify the effect of the Nix configuration on the system.
systemctl status postgresql
systemctl status nginx
Example postgresql status:
● postgresql.service - PostgreSQL RDBMS
Loaded: loaded (/etc/systemd/system/postgresql.service; enabled)
Active: active (running) since Thu 2023-07-10 12:34:56 UTC; 3min ago
Main PID: 1342 (postgres)
A similar output is obtained for nginx. This step demonstrates that the parameters defined in configuration.nix are reflected on the actual system. If a misconfiguration is discovered, simply correct the relevant section in configuration.nix and rerun nixos-rebuild switch; the system will automatically activate the new version.
Reproducibility and Disk Image
To demonstrate reproducibility, copy the same configuration.nix file to another machine and run the same nixos-rebuild switch command. Additionally, NixOS’s nixos-rebuild build-vm command converts the configuration into a QEMU image that can be tested in a CI environment.
nixos-rebuild build-vm
This command produces a disk.qcow2 file under a result directory. In a CI pipeline you can run this image as follows:
- name: Test NixOS VM
run: |
qemu-system-x86_64 -drive file=result/disk.qcow2,format=qcow2,if=virtio \
-m 2048 -nographic -serial mon:stdio -snapshot
Warning: The
nixos-rebuild build-vmcommand creates a virtual‑machine image, and it is important to validate the image in a test environment before running it. Especially before moving to production, ensure the image starts the expected services.
NixOS Architecture and Package Management
NixOS’s package management is built on a pure functional model. Each package is produced by a Nix expression that defines only its inputs (source code, dependencies, build options). This approach guarantees the same output from the same inputs, so the same package on different machines shares the same hash. The nix-env -iA command manages user‑level package installations, while nixos-rebuild integrates system services.
nix-env -iA nixpkgs.htop
Package version certainty is achieved by pinning the nixpkgs channel to a specific revision. For example, the following commands lock to a particular channel version; updating the channel is done with nix-channel --update.
nix-channel --add https://nixos.org/channels/nixos-23.11 nixos
nix-channel --update
Note: Channel URLs and version numbers should be verified against the official NixOS documentation.
Security and Updates
NixOS applies security patches atomically to all packages in the system. The nixos-rebuild switch --upgrade command pulls the latest packages from the defined channels and upgrades the system. This process requires no manual intervention via systemctl; all services are restarted with the new packages.
sudo nixos-rebuild switch --upgrade
When a security vulnerability is discovered, updating the nixpkgs channel to the relevant revision is sufficient. Once the channel revision is updated, the system automatically adopts the new version.
CI/CD Integration: GitHub Actions and NixOS
Integrating declarative configuration into a CI/CD pipeline can be done by adding nix commands directly to a GitHub Actions workflow. The example workflow below builds the configuration.nix in a test VM, runs unit tests, and, if successful, stores the artifact.
name: NixOS CI
on:
push:
branches: [ main ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Install Nix
run: |
curl -L https://nixos.org/nix/install | sh
. $HOME/.nix-profile/etc/profile.d/nix.sh
- name: Build VM
run: nixos-rebuild build-vm
- name: Run Tests
run: |
qemu-system-x86_64 -drive file=result/disk.qcow2,format=qcow2,if=virtio \
-m 2048 -nographic -serial mon:stdio -snapshot -display none \
-monitor none -no-reboot -smp 2 -cpu host
- name: Upload Artifact
uses: actions/upload-artifact@v3
with:
name: nixos-vm-image
path: result/disk.qcow2
This workflow shows how a declarative configuration can be automatically tested and stored as an artifact. A failed build in the CI process is quickly visible in the GitHub UI, preventing a faulty configuration from reaching production.
Rollback and Verification
NixOS simplifies rolling back to a previous system snapshot. When a configuration error is detected, the following command can be used to revert to the last successful build:
sudo nixos-rebuild switch --rollback
Warning: The
--rollbackoption reverts to the previous configuration on the current system. The behavior and availability of this command should be verified in the official documentation for the NixOS version you are using.
After a rollback, it is recommended to check the status of the services again:
systemctl status nginx
systemctl status postgresql
If the rollback succeeds, the services should be in an active (running) state. This mechanism relies on the principle of isolating process errors and re‑evaluating only the affected layer, without needing to rebuild the entire system.
Limitations and Improvement Opportunities
The strengths of the declarative approach include clear version compatibility and explicit package dependencies. However, some situations arise where the exact version you need is not available, or special build flags are missing. In such edge‑cases, you need to customize packages using overrideAttrs or packageOverrides, which can increase the complexity of the configuration file.
{ pkgs, ... }:
{
nixpkgs.overlays = [
(self: super: {
myPython = super.python3.overrideAttrs (old: {
version = "3.10.9";
src = pkgs.fetchFromGitHub {
owner = "python";
repo = "cpython";
rev = "v3.10.9";
sha256 = "0v1c6w7zj..."; # Real hash
};
});
})
];
}
Another trade‑off is that the image‑building process consumes significant CPU and memory during the compilation phase, which can strain resource planning on CI servers.
Conclusion
The most robust approach to this topic is to treat claims and steps as small, verifiable pieces. Write down your assumptions and rollback plan before applying anything; then independently verify the expected outcome. When the environment, version, or conditions change, do not automatically assume the instructions still hold—re‑examine the relevant official sources. This way, decisions are based on an observable, repeatable process rather than a one‑time recipe.
Top comments (0)