Turn the model into muscle memory.
Create a Git repository. Combine the devShells output from lesson 15 with the packages output from lesson 16 inside one outputs attribute set. Keep one shared system binding and one pkgs binding.
Add flake.nix, generate flake.lock, then enter the development shell and run the greeting package. Commit both files. A fresh checkout should resolve the same external inputs on the same supported platform.
git add flake.nix
nix flake lock
nix flake show
nix develop -c python --version
nix build
nix run
nix flake check
git add flake.lock
git commit -m "Add reproducible project environment"
Make the next change a small one.
Update the nixpkgs input, read the lock-file diff, and repeat the checks. Note that nix flake check only runs checks you define, alongside validating recognized output shapes; it cannot prove that your entire application works.
You now have the foundations: the store gives packages distinct identities, derivations describe builds, NixOS modules describe a system, and flakes give projects locked inputs and discoverable outputs. Next, package a real project and explore overlays, tests, or Home Manager when you need them.
Describe → lock → build → inspect → commit. Keep each change small enough to understand.