← Back to Course Index
Lesson 5: Writing flake.nix
Create reproducible development environments
Learning Objectives
- Understand Nix flakes and why they matter
- Write a basic flake.nix for a project
- Define inputs and outputs in flakes
- Create reproducible development shells
- Use flake.lock for pinning dependencies
What is a Flake?
A flake is a modern, reproducible Nix package. It's a project structure that includes:
- flake.nix: Your project manifest (inputs, outputs, dev shell)
- flake.lock: Auto-generated lock file pinning exact versions of all dependencies
Why flakes matter: They solve the "works on my machine" problem by pinning exact versions and making dev environments self-contained and shareable.
Anatomy of a Flake
Here's a minimal flake.nix:
{
description = "My dev environment";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
flake-utils.url = "github:numtide/flake-utils";
};
outputs = { self, nixpkgs, flake-utils }:
flake-utils.lib.eachDefaultSystem (system:
let
pkgs = import nixpkgs { inherit system; };
in
{
devShells.default = pkgs.mkShell {
buildInputs = with pkgs; [
nodejs
python3
git
];
};
}
);
}
Breaking It Down
1. Inputs: Your flake's dependencies
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
flake-utils.url = "github:numtide/flake-utils";
};
2. Outputs: What your flake produces
outputs = { self, nixpkgs, flake-utils }:
{
devShells.default = ...
packages.default = ...
apps.default = ...
};
3. mkShell: Development environment
pkgs.mkShell {
name = "my-dev-env";
buildInputs = with pkgs; [ nodejs python3 gcc ];
shellHook = ''
echo "Dev environment loaded!"
'';
}
Using Your Flake
Enter the dev environment:
nix flake enter # Or: nix develop
Update dependencies:
nix flake update
Try It Yourself
Create Your First Flake
Create a new directory:
mkdir my-nix-project
cd my-nix-project
Create flake.nix:
{
description = "My first Nix dev environment";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
flake-utils.url = "github:numtide/flake-utils";
};
outputs = { self, nixpkgs, flake-utils }:
flake-utils.lib.eachDefaultSystem (system:
let
pkgs = import nixpkgs { inherit system; };
in
{
devShells.default = pkgs.mkShell {
buildInputs = with pkgs; [
nodejs
python3
];
};
}
);
}
Initialize the flake:
nix flake update
This creates flake.lock
Enter the environment:
nix develop
You're now in a shell with node and python available!
Verify your tools are available:
node --version
python3 --version
exit
Key Concepts
flake.lock is crucial: It pins exact versions/commits. With flake.lock in version control, anyone can reproduce the exact same environment.
Key Takeaways
- flake.nix is your project's dependency manifest
- flake.lock ensures reproducibility across machines
- nix develop drops you into the environment
- mkShell is how you define dev environments
- Commit both flake.nix and flake.lock to version control