NixOS Config Breakdown
│ ├── command/ # Bare-metal host (my own hardware)
│ ├── giga/ # Workstation (Gigabyte laptop + eGPU)
│ ├── mail/ # Mail + monitoring hub (Oracle Free Tier)
│ ├── public/ # Public services (Oracle Free Tier)
│ └── vpn/ # VPN gateway + fleet operator (Oracle Free Tier)
├── hosts/common/ # Shared modules
│ ├── global/ # Shared host settings (hardening, zram, DNS zone, health gate)
│ ├── optional/ # Optional services
│ └── users/ # User configs
├── overlays/ # Custom package overrides
├── modules/ # Custom NixOS modules
├── flake.nix # Main flake entry point
└── flake.lock # Dependency lock file
Architecture Philosophy
My infrastructure follows a “think zen garden, not herd” philosophy. Rather than managing dozens of independent servers with disparate configs, I use NixOS Flakes and Modules to maintain a single source of truth for every system.
Core Principles
- Declarative — Every system’s state is defined in code. No manual changes on running servers.
- Reproducible — Copy a system’s config, rebuild, and get an identical system.
- Modular — Common settings live in shared modules; system-specific settings override as needed.
- Version-controlled — The entire OS lives in Git. Changes are reviewed, committed, and deployed.
- Atomic — New configurations are evaluated before activation. Rollback is instant.
- Health-gated — Every deploy settles, checks its critical services, and rolls itself back if one is down.
How a change reaches a host
- Change lands as a pull request; CI evaluates and builds every affected host.
- On merge to
main, a self-hosted runner deploys the affected hosts. - The new generation activates, then a 40-second settle window runs.
- Critical services are checked. Any violation rolls the host back automatically.
- A weekly flake update job moves inputs forward — it is the only unattended path that changes them.
There are no automatic unattended upgrades of the running system: hosts move when main moves.
Host Breakdown
command — Bare-Metal Host
My own hardware, joined to the mesh at 11.0.0.3.
- LUKS full-disk encryption on the root and data disks (btrfs, zstd compression)
- Monitoring agent — ships metrics and logs into the central stack
- Workstation plumbing — PipeWire, USB passthrough, and a virtiofs mount into the desktop
- Hypervisor tooling is present in the config but currently disabled
giga — Workstation
My daily driver — a Gigabyte laptop with an RTX 3090 in an eGPU enclosure.
- AI stack — remote Hermes agent using DeepSeek V4.1 Flash. The local GPU inference stack was retired; the agent now runs on the
vpnhost andgigaconnects to it as a client (TUI, desktop, phone). - Gaming — Proton for the Steam library; the 3090 is the display/rendering GPU, not an inference box
- CI runner — hosts the self-hosted Forgejo Actions runner that deploys the fleet
vpn — VPN Gateway (Oracle Free Tier)
The backbone of my network, at 11.0.0.1. Routes all traffic through WireGuard to a West Montreal datacenter. It also runs the Hermes agent that operates this fleet.
- AmneziaWG — Encrypted WireGuard with jitter for anti-detection
- Unbound — Encrypted DNS resolver (ISPs, govs, or corporations can’t see what sites you visit)
- AdGuard Home — DNS-level ad blocking for the second client tier
- Caddy — the fleet’s only web server (nginx was retired fleet-wide)
- CrowdSec — intrusion detection and IP reputation
- Hermes agent — the operator that files PRs, deploys, and verifies, running on DeepSeek V4.1 Flash
Two client tiers share the same gateway with different DNS postures:
| Interface | Gateway | DNS posture |
|---|---|---|
wg0 |
11.0.0.1 |
fleet mesh — vpn, command, giga, mail, public |
wg1 |
172.16.40.1 |
privacy — Unbound, DNS-over-TLS to Quad9 + Cloudflare |
wg2 |
172.16.41.1 |
security — AdGuard Home filtering |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# hosts/vpn/default.nix
{
networking.hostName = "vpn";
networking.nat.enable = true;
networking.nat.externalInterface = "eth0";
wg-quick.interfaces.wg0 = {
type = "amneziaWG";
listenPort = 41020;
peers = [
{ # command (bare-metal)
publicKey = "...";
allowedIPs = [ "11.0.0.3/32" ];
}
# giga .5, mail .6, public .7 — the rest of the mesh is defined
# in the private flake, alongside the wg1/wg2 client tiers.
];
};
services.unbound = {
enable = true;
settings = {
# Forward all DNS through Quad9 and Cloudflare over TLS
forward-zone = [{
name = ".";
forward-tls-upstream = "yes";
forward-addr = [
"149.112.112.112@853#dns.quad9.net"
"1.0.0.1@853#one.one.one.one"
];
}];
};
};
}
mail — Mail & Monitoring Hub (Oracle Free Tier)
Self-hosted mail server with full email stack, and the host that runs the central observability stack.
- NixOS Mailserver — Postfix, Dovecot, rspamd (filtering), sender-login maps
- DMARC reporting — full email authentication and reporting
- BIMI — brand logo with Verified Mark Certificate
- Caddy — reverse proxy for the mail hostnames
- Monitoring hub — VictoriaMetrics, VMAgent, VictoriaLogs, vlagent, Grafana, Uptime Kuma
- Backups — restic to Cloudflare R2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# hosts/mail/default.nix
{
networking.hostName = "mail";
mailserver = {
enable = true;
fqdn = "mail.ymrtech.com";
domains = [ "ymrtech.com" "trvtk.net" ];
dmarcReporting.enable = true;
fullTextSearch.enable = true;
# Mailboxes and aliases (yannick@, hermes@, notify@, …) are declared
# here; every password comes from SOPS, never from the repo.
accounts = {
"yannick@ymrtech.com".passwordFile =
config.sops.secrets.mail-password.path;
"hermes@ymrtech.com".passwordFile =
config.sops.secrets.hermes-mail-password.path;
};
};
}
public — Public Services (Oracle Free Tier)
The only internet-facing host in the fleet. Everything else is reachable solely over the mesh. Firewall opens just 80/443 TCP (web + ACME), 3478/5349 TCP/UDP (TURN), and the corresponding UDP port range.
- Caddy — fronts every public hostname and terminates TLS
- This website — static Jekyll build, served from a tmpfs by Caddy (prod, staging, and per-PR previews)
- Forgejo —
git.ymrtech.com, the forge behind this config and my CI - Matrix / Synapse —
chat.ymrtech.com - Kanboard —
board.ymrtech.com - Vaultwarden —
vault.ymrtech.com - Client portal + API — the VPN account portal (
/account,/install,/admin) - Attic — Nix binary cache for the fleet
- Honeypot — decoy services plus a dashboard of what probes them
- PostgreSQL — backs the stateful services above
Shared Infrastructure
Common Modules (hosts/common/)
All hosts inherit these shared configurations:
- Fish shell with autocompletion
- zram swap — compressed RAM swap instead of swap on disk
- Kernel hardening — sysctl hardening applied fleet-wide
- Internal DNS zone — mesh names resolve through the fleet resolver
- Crawler ranges — cloud/crawler CIDRs kept current for log and firewall rules
- OpenSSH — password authentication disabled, keys only
- SOPS-nix secrets — encrypted secrets decrypted at activation
- Deploy health gate — settle, check critical services, roll back on violation
- Monitoring agents — VMAgent (metrics) and vlagent (logs) on every host
Monitoring Stack
| Component | Purpose |
|---|---|
| VictoriaMetrics | Metrics storage and query engine |
| VMAgent | Metrics collection and forwarding |
| VictoriaLogs | Log storage, with a 14-day retention window |
| vlagent | Log shipping from every host |
| Grafana | Dashboards and alerting rules |
| Uptime Kuma | External endpoint monitoring |
| autokuma | Publishes monitor state to the public status page |
Security Features
- WireGuard encryption across all internal traffic
- AmneziaWG with jitter for anti-detection on public endpoints
- Encrypted DNS via Unbound + Quad9/Cloudflare over TLS; AdGuard Home posture on the second tier
- CrowdSec — intrusion detection and IP reputation
- Honeypot — decoy services and probe logging on the public host
- DMARC/DKIM/SPF/BIMI for email authentication
- LUKS full-disk encryption on the physical hosts, with zstd compression
- SOPS-managed secrets — no plaintext credentials in the repo
- Health-gated deploys — an unhealthy generation rolls itself back
- Published logging audit — daily verification of what the fleet logs, with the reports published here