A NixOS Spring Festival Journey
Over the Spring Festival holiday, I came across ryan4yin’s blog and found NixOS really intriguing. After tinkering with Linux for so long, I was sick of editing config files under /etc and kept running into dependency conflicts. Every system upgrade would break my environment, and Docker was only a band-aid fix.
What Drew Me to NixOS
- Define your entire system with a single
/etc/nixos/configuration.nixfile - Every configuration update creates a Generation, so you can always roll back from a bad change
- Fully reproducible development environments - just
git clone, no morepacmanorapt
The Foundation of Reproducibility
In NixOS, the /nix/store directory is read-only. It stores all packages, libraries, and configuration files installed through the Nix package manager. Each package has a unique hash-based path, such as /nix/store/l9i65cfgnxgrxlghxg792146w00kafcn-rocksdb-9.8.4/.
This read-only design ensures packages can’t be arbitrarily modified, different versions of the same package can coexist, and if a system update goes wrong, you can roll back instantly. This is the key to NixOS achieving full reproducibility.
Installing NixOS
You can download the Minimal ISO image from the official NixOS website to install on a fresh machine. Alternatively, you can use the nixos-infect script to convert a non-NixOS system into NixOS with one command. The latter has been tested and works on Alibaba Cloud ECS.
The configuration file /etc/nixos/configuration.nix is incredibly straightforward. In the configuration below, we specify the root user’s authorizedKeys and password, set up the SSH server, configure vscode-server, and define the hostname - all from this single file. Once you’ve written the file, just run nixos-rebuild switch and everything is set up.
For my server, I manually partitioned the disk, mounted btrfs, and then ran the nixos-install script. For a more detailed installation guide, check out this blog post: Building Your Dream OS from Scratch. If you’re not entirely confident with the process, you can try it in a virtual machine first. When you’re ready to deploy to a server, simply copy the configuration file over and run nixos-rebuild switch - everything will be identical.
# Edit this configuration file to define what should be installed on
# your system. Help is available in the configuration.nix(5) man page, on
# https://search.nixos.org/options and in the NixOS manual (`nixos-help`).
{ config, lib, pkgs, ... }:
{
imports =
[ # Include the results of the hardware scan.
./hardware-configuration.nix
# vscode server
(fetchTarball {
url = "https://github.com/nix-community/nixos-vscode-server/tarball/master";
sha256 = "09j4kvsxw1d5dvnhbsgih0icbrxqv90nzf0b589rb5z6gnzwjnqf";
})
];
boot.loader.systemd-boot.enable = true;
boot.loader.efi.canTouchEfiVariables = true;
# Pick only one of the below networking options.
# networking.wireless.enable = true;
networking.networkmanager.enable = true;
# Time zone and locale settings
time.timeZone = "Asia/Shanghai";
i18n.defaultLocale = "en_US.UTF-8";
networking.hostName = "hostname";
nixpkgs.config.allowUnfree = true;
# Installed packages
environment.systemPackages = with pkgs; [
openssl
wget
curl
nano
tailscale
];
# Set default editor
environment.variables = {
EDITOR = "nano";
};
# OpenSSH and Tailscale configuration
services.openssh.settings = {
PermitRootLogin = "yes";
PasswordAuthentication = true;
};
# Enable system services, similar to systemctl enable --now xxx
services.openssh.enable=true;
services.tailscale.enable = true;
services.vscode-server.enable = true;
# Root user configuration
users.users.root = {
openssh.authorizedKeys.keys = [
"ssh-rsa AAAAB3NzaC......tTMFP5y8v8= therainisme@qq.com"
];
# Random password I set
password = "4355a46b19d348dc2f57c046f8ef63d4538ebb936000f3c9ee954a27460dd865";
};
system.stateVersion = "24.11";
}
Development Environment Setup (Devbox and Direnv)
In software development, environment configuration has always been a headache. Different projects have varying requirements for compiler versions, dependency libraries, and environment variables, often leading to the classic “works on my machine” problem. This section introduces Devbox and Direnv for managing development environments, using two typical Go projects as examples to demonstrate how to achieve precise control over your dev environment.
Truth be told, these two tools work even better on non-NixOS systems than they do on NixOS itself.
Prerequisites
Before all the examples, you must install devbox and direnv, and add a direnv plugin to your IDE. If you’re using NixOS, add the following configuration and run nixos-rebuild switch:
programs.direnv.enable = true;
environment.systemPackages = with pkgs; [
...
devbox
];
Example 1: A Go Program with CGO Enabled, Linked Against RocksDB v9.8.4
You need to compile a Go program that uses CGO and links against RocksDB v9.8.4. The complexity of this scenario lies in:
- CGO support must be enabled
- A specific version of a C library (RocksDB) is required
- pkg-config is needed to locate header file paths
First, navigate to the project’s root directory and run:
# Initialize Devbox project
devbox init
# Add pinned version dependencies
devbox add rocksdb@9.8.4 # Specify RocksDB version; defaults to @latest if omitted
devbox add pkg-config
devbox add gcc
# Generate Direnv configuration
devbox generate direnv
Now open a shell and run pkg-config --cflags --libs rocksdb - everything is already configured. It’s that simple.
$ pkg-config --cflags --libs rocksdb
-I/nix/store/l9i65cfgnxgrxlghxg792146w00kafcn-rocksdb-9.8.4/include -L/nix/store/l9i65cfgnxgrxlghxg792146w00kafcn-rocksdb-9.8.4/lib -lrocksdb
Example 2: A Legacy Go Program That Requires Go 1.20.3
First, navigate to the project’s root directory and run:
devbox init
devbox add go@1.20.3 # Pin the exact Go version
devbox generate direnv
Now open a shell and verify with these steps:
# Verify within the project directory
$ go version
go version go1.20.3 linux/amd64
# Check environment isolation
$ which go
/home/user/.devbox/nix/profile/default/bin/go
More Miscellaneous Configuration
TODO
Nixpkg Resources
- https://search.nixos.org/options - Search for configuration options in
/etc/nixos/configuration.nix. For example, to install Docker, according to its description, you only need to add one line:virtualisation.docker.enable = true; - https://search.nixos.org/packages - Search nixpkgs. Unless the package is extremely obscure, you’ll find it here. However, older versions of packages are not available - for instance, Go 1.20 can’t be found in the 24.11 channel.
- https://www.nixhub.io/ - Devbox’s package search website, where you can find both the latest packages and all historical versions. For example, Go 1.20 is available here.
Common Devbox Commands
devbox init: Initialize devbox for a project. After running, adevbox.jsonfile appears in the project directory. You can either edit the file directly to add packages or usedevbox add, similar to package managers like npm.devbox add: Add a package. You can specify a version with @xxx, e.g.devbox add rocksdb@9.8.4.dev search: Search for packagesdev rm: Remove a packagedevbox generate direnv: Automatically generate an.envrcfile for direnv. Once set up, the environment loads automatically.devbox shell: Manually enter the environment shelldevbox run: Manually run a command (add scripts under theshell.scriptsfield indevbox.json, similar to thescriptsfield in Node’spackage.json)