title | sub_title | author | options | theme | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
How to Nixify your codebase |
Docker is a shitcoin |
Jose Storopoli, Alpen Labs, storopoli.io, stratabtc.org |
|
|
https://reduced.to/mvqkh
- Why Nix? What makes it unique (5 min)
- Nix language basics (5 min)
- Anatomy of a Nix derivation (5 min)
- Anatomy of a Flake (5 min)
- Running Nix locally & in CI (5 min)
- Hands-on: Build your own Flake (30 min)
- Wrap-up (5 min)
- Docker and VMs help isolate environments,
but they can lack precise reproducibility.
- What happens if we do a
RUN sudo apt install foo
in different points of time/space?
- What happens if we do a
- Dependency hell is real: "works on my machine" problem.
- Purely functional package manager: no side effects.
- Reproducible: every build is guaranteed to be the same everywhere.
- Immutable: no accidental mutations.
- Native support:
aarch64-darwin
,aarch64-linux
,i686-linux
,x86_64-darwin
,x86_64-linux
, etc.
- Never worry about environment drift.
- Reproducibility at the level of packages and entire systems.
- Perfect for long-lasting Bitcoin projects.
- Define what you want, not how.
- Evaluate only when needed.
{ pkgs }: # A simple derivation
pkgs.mkShell {
buildInputs = [ pkgs.hello ];
}
- Nix values do not change after definition.
- You can compose small functions to build larger systems.
- Everything in Nix is an expression.
- Data types: strings, lists, sets (similar to dictionaries).
let
a = "hello";
b = "world";
in
"${a} ${b}"
Try this in a
nix repl
!
- A derivation is the fundamental unit in Nix.
- It describes a package or environment: how it's built, from what inputs, and what outputs.
stdenv.mkDerivation {
pname = "my-package";
version = "1.0";
src = ./my-package-source;
buildInputs = [ pkgs.lib ];
buildPhase = ''
make
'';
installPhase = ''
make install
'';
}
- Inputs: Dependencies needed.
- Build process: How to compile or run the package.
- Outputs: What gets produced (e.g., binaries, libraries).
- Isolation: Builds happen in sandboxed environments without explicit internet access.
- A standardized interface for Nix projects.
- Reproducible and composable.
{
description = "My flake project";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.05";
};
outputs = { self, nixpkgs }: {
packages.x86_64-linux.default = nixpkgs.lib.mkShell {
buildInputs = [ nixpkgs.hello ];
};
};
}
- Inputs: External dependencies, typically from git repositories.
- Outputs: What this flake produces (packages, NixOS configurations, apps).
- Benefits: Easier composition of projects, guaranteed reproducibility.
- Easy install: just use
DeterministicSystems/nix-installer
.
curl --proto '=https' --tlsv1.2 -fsSL https://install.determinate.systems/nix | sh -s -- install
-
Simple command:
nix develop
ornix run
-
Spin up development environments instantly.
nix develop . # Activate flake environment
- Use Nix in GitHub Actions, GitLab, or custom CI pipelines for perfect reproducibility.
- No surprises between development and production environments.
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install Nix
uses: DeterminateSystems/nix-installer-action@v14
- name: Build
run: nix build . # or nix build .#my-flake
We're going to build a simple Rust project that runs a bitcoind
command.
- I know Rust so I can help you.
Let's start a binary Rust project in a directory:
nix run nixpkgs#cargo init .
This will create the following structure:
.
├── Cargo.toml
├── src
└───── main.rs
Modify you main.rs
:
use std::{
io::Read,
process::{Command, Stdio},
};
fn main() {
// Run the bitcoin-cli command
let output = Command::new("bitcoin-cli")
.arg("--regtest")
.arg("getblockchaininfo")
.stdout(Stdio::piped()) // Capture the standard output
.spawn() // Start the command
.expect("Failed to execute bitcoin-cli");
// Wait for the command to complete and capture the output
let mut child_stdout = output
.stdout
.expect("Failed to capture stdout");
// Read the output from the process
let mut output_string = String::new();
child_stdout.read_to_string(&mut output_string)
.expect("Failed to read command output");
// Print the result of bitcoin-cli
println!("Output from bitcoin-cli: {}", output_string);
}
Run it and see if it works:
nix run nixpkgs#cargo run
Let's grab one of the Flake templates (you can find them at GitHub NixOS/templates
):
nix flake init --template templates#trivial
This creates the following flake.nix
:
{
description = "A very basic flake";
inputs = {
nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable";
};
outputs = { self, nixpkgs }: {
packages.x86_64-linux.hello = nixpkgs.legacyPackages.x86_64-linux.hello;
packages.x86_64-linux.default = self.packages.x86_64-linux.hello;
};
}
Let's run it:
nix build .#my-flake
Should work and create a result
symlink:
./result/bin/hello
It also creates a flake.lock
file that pins the dependencies.
This is where the magic happens: Nix will fetch the dependencies and build them in a sandboxed environment.
First, let's modify the Flake to help us out with different target architectures:
{
description = "A very basic flake";
inputs = {
nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable";
utils.url = "github:numtide/flake-utils";
};
outputs = { self, nixpkgs, utils }:
utils.lib.eachDefaultSystem (system:
let
pkgs = import nixpkgs { inherit system; };
in
{
defaultPackage = pkgs.hello;
}
);
}
Now we need a development environment that has bitcoind
running in Regtest.
We need to add a new derivation in the outputs
:
in
{
# ...
devShell = pkgs.mkShell {
buildInputs = [ pkgs.cargo pkgs.bitcoind ];
shellHook = ''
${pkgs.bitcoind}/bin/bitcoind -regtest -daemon
'';
};
}
Enter the shell:
nix develop .
Now cargo run
will work in any system.
Finally let's build our package using buildRustPackage
:
defaultPackage = pkgs.rustPlatform.buildRustPackage {
pname = "nix-workshop";
version = "0.1.0";
cargoLock = {
lockFile = ./Cargo.lock;
};
src = ./.;
buildInputs = [
pkgs.rustc
pkgs.cargo
pkgs.bitcoind
];
preBuild = ''
mkdir -p $out/data
${pkgs.bitcoind}/bin/bitcoind -regtest -daemon -datadir=$out/data
'';
};
BOOM! We can run with:
# make sure you're in the flake shell
nix build .
./result/bin/nix-workshop
- Nix provides reliability and reproducibility that no other solution matches.
- Perfect for long-lived, critical projects like Bitcoin.
- Declarative, lazy, and purely functional — a paradigm shift in managing environments.