diff --git a/readme.md b/readme.md index babbab4..c1642e2 100644 --- a/readme.md +++ b/readme.md @@ -1,8 +1,8 @@ -This post is a Flake-based rewrite of [Learn Nix the Fun Way on fzakaria.com] -[0]. I really enjoyed the content of the post and wanted to write it as a Nix -user who is just using flakes. It does add a few extra steps and complexity, -but I think it's still valuable and perhaps reveals a bit more about Nix and why -it's pretty fantastic. +This post is a Flake-based rewrite of [Learn Nix the Fun Way on fzakaria.com][0]. +I really enjoyed the content of the post and wanted to write it as a Nix +user who is just using and prefers flakes. It does add a few extra steps and +complexity, but I think it's still valuable and perhaps reveals a bit more about +Nix and why it's pretty fantastic. @@ -12,15 +12,15 @@ Let's walk through a single example of a shell script one may write: _what-is-my ```bash #!/usr/bin/env bash -curl -s http://httpbin.org/get \ - | jq --raw-output .origin +curl -s http://httpbin.org/get | \ + jq --raw-output .origin ``` Sure, it's _sort of portable_, if you tell the person running it to have _curl_ and _jq_. What if you relied on a specific version of either though? Nix **guarantees** portability. -We might leverage _[Nixpkgs' trivial builders](https://ryantm.github.io/nixpkgs/builders/trivial-builders/)_ (specifically, `writeShellScriptBin`) in a basic Nix flake to turn this into a Nix derivation (i.e. build recipe). +We might leverage _[Nixpkgs' trivial builders](https://ryantm.github.io/nixpkgs/builders/trivial-builders/)_ (specifically, `writeShellScriptBin`) in a basic Nix flake to turn this into a Nix derivation (i.e. build recipe): ```nix # flake.nix @@ -50,9 +50,13 @@ pkgs.writeShellScriptBin "what-is-my-ip" '' Here we are pinning our package to dependencies which come from NixOS/Nixpkgs release branch 24.05. -If we `nix build` and `readlink result` we get: +We can build our package and find out the Nix store path (which contains the +hash) like so: - **/nix/store/lr6wlz2652r35rwzc79samg77l6iqmii-what-is-my-ip** +```console +$ nix build --print-out-paths +/nix/store/lr6wlz2652r35rwzc79samg77l6iqmii-what-is-my-ip +``` And, of course, we can run our built result: @@ -68,7 +72,9 @@ $ nix run 24.5.113.148 ``` -Now that this is in Nix and we've modeled our dependencies, we can do _fun_ things like generate graph diagrams to view them (click the image to view larger). +Now that this is in our Nix store, we've naturally modeled our dependencies +and can do _fun_ things like generate graph diagrams (click the image to view +larger): ```console $ nix-store --query --graph $(readlink result) | nix shell nixpkgs#graphviz -c dot -Tpng -o what-is-my-ip-deps.png @@ -76,7 +82,7 @@ $ nix-store --query --graph $(readlink result) | nix shell nixpkgs#graphviz -c d [![Image of what-is-my-ip dependencies as a graph](./what-is-my-ip-deps.png)](./what-is-my-ip-deps.png) -Let's create a _developer environment_ and bring in our new tool. +Let's add a _developer environment_ which contains our new tool. This is a great way to create developer environments with reproducible tools. ```diff @@ -89,7 +95,7 @@ index 2a99357..ab32421 100644 inputs.nixpkgs.url = "nixpkgs/nixos-24.05"; - outputs = {nixpkgs, ...}: let + outputs = { -+ self, ++ self, # we will need to reference our own outputs to pull in the package we've declared + nixpkgs, + ... + }: let @@ -97,12 +103,12 @@ index 2a99357..ab32421 100644 pkgsFor = func: (nixpkgs.lib.genAttrs systems (system: (func (import nixpkgs {inherit system;})))); in { packages = pkgsFor (pkgs: { - what-is-my-ip = pkgs.callPackage ./what-is-my-ip.nix {}; + default = pkgs.callPackage ./what-is-my-ip.nix {}; }); + + devShells = pkgsFor (pkgs: { + default = pkgs.mkShell { -+ packages = [self.outputs.packages.${pkgs.system}.what-is-my-ip]; ++ packages = [self.outputs.packages.${pkgs.system}.default]; + shellHook = '' + echo "Hello, Nix!" + ''; @@ -122,7 +128,7 @@ $ which what-is-my-ip 🕵️ Notice that the hash **lr6wlz2652r35rwzc79samg77l6iqmii** is _exactly_ the same which we built earlier. -We can now do binary or source deployments 🚀🛠️📦 since we know the full dependency closure of our tool. We simply copy the necessary _/nix/store_ paths to another machine with Nix installed. +We can now do binary or source deployments 🚀🛠️📦 since we know the full dependency closure of our tool. We simply copy the necessary `/nix/store` paths to another machine with Nix installed. ```console $ nix copy --to ssh://beefcake $(nix build --print-out-paths) @@ -130,7 +136,7 @@ $ ssh beefcake /nix/store/lr6wlz2652r35rwzc79samg77l6iqmii-what-is-my-ip/bin/wha 98.147.178.19 ``` -Maybe though you are stuck with Kubernetes or Docker. Let's use Nix to create an OCI-compatible image. +Maybe though you are stuck with Kubernetes or Docker. Let's use Nix to create an OCI-compatible image with our tool: ```diff diff --git a/flake.nix b/flake.nix @@ -155,13 +161,13 @@ index 99d6d52..81e98c9 100644 ```console $ docker load < $(nix build .#docker-image --print-out-paths) Loaded image: what-is-my-ip-docker:c9g6x30invdq1bjfah3w1aw5w52vkdfn -$ docker run -it what-is-my-ip-docker:c9g6x30invdq1bjfah3w1aw5w52vkdfn +$ docker run -it what-is-my-ip-container:c9g6x30invdq1bjfah3w1aw5w52vkdfn 24.5.113.148 ``` Cool! Nix + Docker integration perfectly. The image produced has only the files exactly necessary to run the tool provided, effectively **distroless**. You may also note that if you are following along, your image digest is exactly the same. **Reproducibility!** -Finally, let's take the last step and create a reproducible operating system using NixOS to contain only the programs we want. +Finally, let's take the last step and create a reproducible operating system using NixOS to contain only the programs we want: ```diff diff --git a/flake.nix b/flake.nix @@ -198,6 +204,8 @@ index 99d6d52..81e98c9 100644 } ``` +Now we can run this NixOS configuration as a reproducible virtual machine: + ```console $ nixos-rebuild build-vm --flake .#default $ ./result/bin/run-nixos-vm @@ -217,6 +225,40 @@ $ what-is-my-ip We took a relatively simple script through a variety of applications in the Nix ecosystem: build recipe, shell, docker image, and finally NixOS VM. +One of the super neat part about flakes is that anywhere you find a flake, you +can make use of it. Try it out now! + +> Note: The following obviously runs code from the internet. Be wary of doing this in general. + +```console +# run a flake's package +$ nix run git+https://git.lyte.dev/lytedev/learn-flakes-the-fun-way +24.5.113.148 + +# enter a flake's development environment +$ nix develop git+https://git.lyte.dev/lytedev/learn-flakes-the-fun-way -c $SHELL +Hello, Nix! +$ what-is-my-ip +24.5.113.148 + +# load a flake's docker image and run it +$ docker load < $(nix build git+https://git.lyte.dev/lytedev/learn-flakes-the-fun-way#container --print-out-paths) +Loaded image: what-is-my-ip-container:wg0z43v4sc1qhq7rsqg02w80vsfk9dl0 +$ docker run -it what-is-my-ip-container:c9g6x30invdq1bjfah3w1aw5w52vkdfn + +# run a flake's NixOS configuration as a virtual machine +$ nixos-rebuild build-vm --flake git+https://git.lyte.dev/lytedev/learn-flakes-the-fun-way#default +Done. The virtual machine can be started by running /nix/store/xswwdly9m5bwhcz9ajd6km5hx9vdmfzw-nixos-vm/bin/run-nixos-vm +$ /nix/store/xswwdly9m5bwhcz9ajd6km5hx9vdmfzw-nixos-vm/bin/run-nixos-vm + +# like running a pre-configured version of my workstation's NixOS configuration +# NOTE: this will probably take a good, long time to build and lots of bandwidth +$ nixos-rebuild build-vm --flake git+https://git.lyte.dev/lytedev/nix#dragon +Done. The virtual machine can be started by running /nix/store/abc-nixos-vm/bin/run-nixos-vm +$ /nix/store/abc-nixos-vm/bin/run-nixos-vm + +``` + Hopefully, seeing the _fun things_ you can do with Nix might inspire you to push through the hard parts. There is a golden pot 💰 at the end of this rainbow 🌈 awaiting you.