Nix in Franklyn
Franklyn uses Nix to make developer environments and CI runs predictable across machines.
The short version:
- We define
fr-scripts that are used in CI and when working insidenix develop. - Build dependencies live in Nix dev shells (so CI and local development use the same toolchain).
- Some parts also ship Nix derivations that build real artifacts (not just a dev environment).
- The flake is structured with
flake-partsto keep each subproject’s Nix logic modular.
Why we use Nix here
Franklyn spans multiple toolchains (Maven/Java, Rust, Bun/Node, Hugo/Go). Without a shared environment definition you typically get:
- “works on my machine” differences (tool versions, system libraries, missing CLI tools)
- CI behaving differently from local runs
- onboarding friction for a new team
Nix addresses this by describing environments and build inputs declaratively. Developers enter the same environment CI uses, and the project controls the tool versions.
How we use Nix (the pattern)
There are two main layers:
- Dev shells (
nix develop)
- Provide all build dependencies for a given subproject.
- Provide the
fr-scripts (the stable entrypoints).
- Packages / derivations (
nix build)
- Build artifacts in a reproducible way (for releases and CI verification in some cases).
The key idea is: use the same command entrypoints locally and in CI.
Where to find the Nix code
- Root flake:
flake.nix - Subproject Nix modules:
- Hugo:
hugo/default.nix - Sentinel:
sentinel/default.nix - Proctor:
proctor/default.nix - Server:
server/default.nix
- Hugo:
CI references:
- PR checks:
.github/workflows/pr-checks.yaml - Release builds:
.github/workflows/release.yaml - Docs build/deploy:
.github/workflows/hugo-deploy.yaml - Nix + caches setup action:
.github/actions/setup-nix/action.yaml
flake-parts in this repository
flake.nix uses flake-parts.lib.mkFlake and imports the subproject modules (./hugo, ./sentinel, ./proctor, ./server).
What this does for us:
- Keeps Nix definitions close to each subproject.
- Uses
perSystem = { ... }so outputs are produced for each supported platform. - Provides shared arguments (for example
project-version,package-meta, apkgswith overlays, andmkEnvHook).
If you are new to flakes and perSystem, this is the minimal mental model:
- A flake exposes “outputs” (dev shells, packages, etc.).
perSystemmeans those outputs are computed for eachsystem(for examplex86_64-linux).
Official docs:
- Nix manual (flakes, commands): https://nixos.org/manual/nix/stable/
- flake-parts: https://flake.parts/
Dev shells and fr- scripts
Each subproject defines a dev shell (for example devShells.server, devShells.sentinel, …). Inside the shell definition we include:
- the toolchain packages needed for that subproject
- the
fr-scripts created viapkgs.writeScriptBin
Examples in this repo:
- Server scripts in
server/default.nix:fr-server-build-clean,fr-server-pr-check - Sentinel scripts in
sentinel/default.nix:fr-sentinel-pr-check,fr-sentinel-build,fr-sentinel-coverage - Proctor scripts in
proctor/default.nix:fr-proctor-pr-check,fr-proctor-build - Hugo scripts in
hugo/default.nix:fr-hugo-build
Why scripts instead of “just run mvn/cargo/bun”?
- CI can call one stable command per project.
- Local developers run the exact same checks.
- The scripts document the intended workflow in one place.
Derivations (packages) we build
Some components also define Nix packages (derivations) that build artifacts:
- Server:
packages.franklyn-serverinserver/default.nix(builds a runnable JAR) - Sentinel:
packages.franklyn-sentinelandpackages.franklyn-sentinel-debinsentinel/default.nix(binary + Debian package)
These are used in CI (nix build .#...) and are useful for release automation because they produce well-defined outputs.
How CI uses Nix
In GitHub Actions we:
- install Nix
- enable caching (Cachix) to speed up builds
- enter the relevant dev shell and run the
fr-script - sometimes also build the derivation to ensure the package builds (
nix build .#...)
See .github/actions/setup-nix/action.yaml and the workflows listed earlier.
New to Nix: a few basics (and where to read more)
What is nix develop?
- It drops you into a shell environment defined by the project.
- The environment contains the tools and scripts we want you to use.
What is nix build?
- It builds a specific package output from the flake.
- You typically use it when you want an artifact (a binary, a JAR, a .deb, generated files).
What does .#server or .#franklyn-server mean?
.means “this flake” (the current repository).#nameselects one output (a dev shell, package, etc.) by name.
If you want to go deeper:
- Nix manual: https://nixos.org/manual/nix/stable/
- nixpkgs manual (packaging concepts): https://nixos.org/manual/nixpkgs/stable/