Skip to content

Instantly share code, notes, and snippets.

@storopoli
Last active June 23, 2025 20:11
Show Gist options
  • Save storopoli/30cae4dfbf21c870b93737cfc87a0d90 to your computer and use it in GitHub Desktop.
Save storopoli/30cae4dfbf21c870b93737cfc87a0d90 to your computer and use it in GitHub Desktop.
Nixify your Codebase Workshop
title sub_title author options theme
How to Nixify your codebase
Docker is a shitcoin
Jose Storopoli, Alpen Labs, storopoli.io, stratabtc.org
incremental_lists
true
override
code
alignment background
left
false

Slides

https://reduced.to/mvqkh

Agenda

  1. Why Nix? What makes it unique (5 min)
  2. Nix language basics (5 min)
  3. Anatomy of a Nix derivation (5 min)
  4. Anatomy of a Flake (5 min)
  5. Running Nix locally & in CI (5 min)
  6. Hands-on: Build your own Flake (30 min)
  7. Wrap-up (5 min)

Why Nix?

Docker, VMs, and their limits

  • 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?
  • Dependency hell is real: "works on my machine" problem.

What Nix Brings

  • 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.

Nix = Reliability

  • Never worry about environment drift.
  • Reproducibility at the level of packages and entire systems.
  • Perfect for long-lasting Bitcoin projects.

Nix Language Basics

Declarative & Lazy-Evaluated

  • Define what you want, not how.
  • Evaluate only when needed.
{ pkgs }:  # A simple derivation
pkgs.mkShell {
  buildInputs = [ pkgs.hello ];
}

Functional

  • Nix values do not change after definition.
  • You can compose small functions to build larger systems.

Nix Language Basics

Language Concept: Expressions

  • 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!

Anatomy of a Nix Derivation

What is a Derivation?

  • 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
  '';
}

Anatomy of a Nix Derivation

Derivation Key Elements

  • 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.

Anatomy of a Flake

What is a Flake?

  • 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 ];
    };
  };
}

Anatomy of a Flake

Flake Key Elements

  • Inputs: External dependencies, typically from git repositories.
  • Outputs: What this flake produces (packages, NixOS configurations, apps).
  • Benefits: Easier composition of projects, guaranteed reproducibility.

Running Nix Locally & in CI

Local Development

  • 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 or nix run

  • Spin up development environments instantly.

nix develop .  # Activate flake environment

Running Nix Locally & in CI

CI Integration

  • 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

Hands-on: Build Your Own Flake

We're going to build a simple Rust project that runs a bitcoind command.

Why Rust?

  • I know Rust so I can help you.

Hands-on: Build Your Own Flake

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

Hands-on: Build Your Own Flake

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);
}

Hands-on: Build Your Own Flake

Run it and see if it works:

nix run nixpkgs#cargo run

Hands-on: Build Your Own Flake

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;

  };
}

Hands-on: Build Your Own Flake

Let's run it:

nix build .#my-flake

Should work and create a result symlink:

./result/bin/hello

Hands-on: Build Your Own Flake

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.

Hands-on: Build Your Own Flake

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;
      }
    );
}

Hands-on: Build Your Own Flake

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
          '';
        };
      }

Hands-on: Build Your Own Flake

Enter the shell:

nix develop .

Now cargo run will work in any system.

Hands-on: Build Your Own Flake

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
  '';
};

Hands-on: Build Your Own Flake

BOOM! We can run with:

# make sure you're in the flake shell
nix build .
./result/bin/nix-workshop

Hands-on: Wrap-up

Key Takeaways

  • 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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment