<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>flakes &amp;mdash; A Bit</title>
    <link>https://baez.link/tag:flakes</link>
    <description>A little bit of writing by Alejandro </description>
    <pubDate>Thu, 16 Apr 2026 01:59:09 +0000</pubDate>
    <item>
      <title>Getting Started Using Nix Flakes As An Elixir Development Environment</title>
      <link>https://baez.link/getting-started-using-nix-flakes-as-an-elixir-development-environment?pk_campaign=rss-feed</link>
      <description>&lt;![CDATA[Never is a project started from &#39;just&#39; the init. You have to take care of packages you use, CI tools for builds you make, database hookups, development tooling, and countless other parts. All of this takes time. With nix flakes, you may be able to start with all the main components you need immediately. Giving way to actually developing that app you been itching to build, without the days/weeks adventure getting everything you need just right.&#xA;&#xA;!--more--&#xA;&#xA;Now it doesn&#39;t mean that immediately reading this starter guide, you will have everything under the sun set up with Nix Flakes for your development need. But at least, you won&#39;t have to worry about setting up asdf, your weird hacks you need for your machine and the other tiny little things to get elixir started with elixir-ls.&#xA;&#xA;Background&#xA;&#xA;A little background. Nix, for the uninitiated, is a purely functional language for package management. What makes Nix interesting is you can use the purely functional aspect to build out artifacts which are entirely idempotent. Meaning, no matter how many times you run the nix expressions you have, the end result will always be the same regardless of external state of a machine. Its build structure as a package manager has evolved the language to build out guaranteed result for all sorts of software. Yet, Nix itself isn&#39;t exactly easy to learn. Due in part to the ambitions of the project and difficulties, which arose from those ambitions, complexities crept in. Nix Flakes is an answer to some of these complexities and more. &#xA;&#xA;With Nix Flakes, you have the ability to have a very well defined package for a project. Written and using Nix in a way that takes all the learning from its years. Making it easier to define what you want from nix; the build result for a package.  &#xA;&#xA;Getting flakes enabled&#xA;&#xA;So how do you get started with Nix Flakes? The first part that you should probably have is nix already installed and some familiarity with the nix language. Run through the guide to your platform needs.&#xA;&#xA;Next is the settings to use nix with flakes. Since flakes is still in development (but relatively stable), you do need to enable the feature on nix. You can do so by enabling the experimental features of both nix-command and flakes through the nix.conf file.&#xA;&#xA;make nix config path if not existent&#xA;mkdir ~/.config/nix/ -p&#xA;add the settings to the file on config path&#xA;echo &#34;experimental-features = nix-command flakes&#34; | tee ~/.config/nix/nix.conf -a&#xA;&#xA;Once the settings are applied, you should be able to validate by running show-config&#xA;&#xA;nix show-config | grep experimental&#xA;&#xA;If all is successful, you should see an output for the same experimental features you wrote on the nix.conf file.&#xA;&#xA;The flake.nix file&#xA;&#xA;So now to get to use nix flake comes the heart of the project, the flake.nix file. The file itself needs three defined keys on the set, description, inputs, and outputs. Each one of them plays a different role in how to define your package you want to build. &#xA;&#xA;description key&#xA;&#xA;The description key is a one liner description of what the flake project is. Helps in giving what the flake is for quick review after you have a few hundred of these built out...&#xA;&#xA;flake.nix, ignoring input and output&#xA;{&#xA;  description = &#34;A description of some kind&#34;;&#xA;}&#xA;&#xA;inputs key&#xA;&#xA;The inputs is how you can import external sources of other flakes into the flake project you have. In other words, any project you may need or tools required to get started, this is where you will define their source.  Example below is using the standard nixpkgs and a tool called flake-utils, which provides a set of functions to make flake nix packages simpler to set up without external dependencies.&#xA;&#xA;flake.nix, ignoring description and output&#xA;{&#xA;  inputs = {&#xA;    # using unstable branch for the latest packages of nixpkgs&#xA;    nixpkgs = { url = &#34;github:NixOS/nixpkgs/nixpkgs-unstable&#34;; }; &#xA;    flake-utils = { url = &#34;github:numtide/flake-utils&#34;; };&#xA;  };&#xA;}&#xA;&#xA;outputs key&#xA;&#xA;The outputs is where the bulk of your logic for what you will build with flakes. It has quite a numerous of options, but in this use case, we only care of the devShell key, which is what will be used to populate the development environment. While the following may all look like a lot for the output, it&#39;s also everything that would be needed for either building out a nix flake package or use for a development environment: &#xA;&#xA;flake.nix, ignoreing description and input&#xA;{&#xA;  outputs = { self, nixpkgs, flake-utils }:&#xA;   flake-utils.lib.eachDefaultSystem (system:&#xA;      let&#xA;        pkgs = import nixpkgs { inherit system; };&#xA;&#xA;        elixir = pkgs.beam.packages.erlang.elixir;&#xA;        elixir-ls = pkgs.beam.packages.erlang.elixirls;&#xA;        locales = pkgs.glibcLocales;&#xA;      in&#xA;      {&#xA;        devShell = pkgs.mkShell {&#xA;          buildInputs = [&#xA;            elixir&#xA;            locales&#xA;          ]&#xA;        }&#xA;      });&#xA;}&#xA;&#xA;The output key is actually a function which takes the inputs defined on inputs. Hence the set { self, nixpkgs, flake-utils }. All the inputs on the function were defined on the input with self being the flake.nix file itself. The next portion is using a simple but powerful flake-utils function called eachDefaultSystem. What the function provides is actually build the development environment for all available platforms currently available for nix as a default. You can see the list by running nix flake show  (after the file is fully written) and you will be provided an output like the following: &#xA;&#xA;└───devShell&#xA;    ├───aarch64-darwin: development environment &#39;nix-shell&#39;&#xA;    ├───aarch64-linux: development environment &#39;nix-shell&#39;&#xA;    ├───i686-linux: development environment &#39;nix-shell&#39;&#xA;    ├───x8664-darwin: development environment &#39;nix-shell&#39;&#xA;    └───x8664-linux: development environment &#39;nix-shell&#39;&#xA;&#xA;Meaning, you do not have to worry about what OS you using, as long as it&#39;s linux or macos. You write your nix flake with the ability to use it with all the supported platforms from the start for your environment. In other words, Write once, run on all the machines. No more of that &#39;it runs on my machine&#39; debacle. &#xA;&#xA;The last section is a let .. in pair to both declare what you will use in the system you are building and devShell itself. The packages use here are simply elixir, elixir-ls for sanity, and locales to make sure elixir is able to use the proper locale settings of the shell environment produced by the nix flake. &#xA;&#xA;Finally, the devShell in this case is the buildInputs wanted for the shell environment and nothing more: &#xA;&#xA;      {&#xA;        devShell = pkgs.mkShell {&#xA;          buildInputs = [&#xA;            elixir&#xA;            locales&#xA;          ]&#xA;      }&#xA;&#xA;Notice while elixir-ls package isn&#39;t directly declared in the mkShell buildInput, it is part of the output on let. Allowing to still have linked access to its packages.&#xA;&#xA;The full nix flake development environment&#xA;&#xA;Finally, putting it all together, getting a nix flake project started for your development environment, all falls down to what packages you need on the devShell.  With the flake.nix file on the root of your project, you have the ability to have the packages you need and ready for you to work with. &#xA;&#xA;{&#xA;    description = &#34;Development environment&#34;;&#xA;&#xA;  inputs = {&#xA;      nixpkgs = { url = &#34;github:NixOS/nixpkgs/nixpkgs-unstable&#34;; };&#xA;    flake-utils = { url = &#34;github:numtide/flake-utils&#34;; };&#xA;  };&#xA;&#xA;  outputs = { self, nixpkgs, flake-utils }:&#xA;    flake-utils.lib.eachDefaultSystem (system:&#xA;      let&#xA;        inherit (nixpkgs.lib) optional;&#xA;        pkgs = import nixpkgs { inherit system; };&#xA;&#xA;        elixir = pkgs.beam.packages.erlang.elixir;&#xA;        elixir-ls = pkgs.beam.packages.erlang.elixirls;&#xA;        locales = pkgs.glibcLocales;&#xA;      in&#xA;      {&#xA;          devShell = pkgs.mkShell&#xA;          {&#xA;              buildInputs = [&#xA;                elixir&#xA;              locales&#xA;            ];&#xA;          };&#xA;      }&#xA;    );&#xA;}&#xA;#elixir #development #nix #flakes &#xA;&#xA;[1]: https://nixos.org/manual/nix/stable/introduction.html&#xA;[2]: https://nixos.org/manual/nix/unstable/command-ref/new-cli/nix3-flake.html&#xA;[3]: https://nixos.wiki/wiki/Flakes&#xA;[4]: https://peppe.rs/posts/novicenix:flaketemplates/&#xA;[5]: https://nixos.org/guides/install-nix.html&#xA;[6]: https://nixos.org/manual/nix/unstable/command-ref/conf-file.html&#xA;[7]: https://nixos.wiki/wiki/NixExpressionLanguage&#xA;[8]: https://github.com/numtide/flake-utils&#xA;[9]: https://nixos.wiki/wiki/Flakes#Outputschema&#xA;[10]: https://github.com/elixir-lsp/elixir-ls&#xA;&#xA;Bonus: Limiting by platform of choice your nix flake&#xA;&#xA;Glad you made it this far. Well friend, besides the default system listing on flake-utils, there is another route you can go in setting up you development environment. You may have noticed that the run of nix flake show showed multiple platforms and architectures available by default. However, it may be you don&#39;t need to use all those platforms and architecture. You may like to target only a set of architectures or platforms you need and that&#39;s it. Speeding up your build process and saving on some storage if you don&#39;t want those extra platforms.&#xA;&#xA;flake-utils has the option with the flake-utils.lib.eachSystem function. The function itself takes an array of systems you want the flake to build out. To use the function, you have to use a let .. in expression to define the array and then it&#39;s use. For my use, I only target aarch64-linux and x8664-linux since those are the only platforms I work with. So I define them on an array. &#xA;&#xA;supportedSystems = [ &#34;x8664-linux&#34; &#34;aarch64-linux&#34; ];&#xA;Then I use that defined array to apply to the the function flake-utils.lib.eachSystem&#xA;&#xA;flake-utils.lib.eachSystem supportedSystems (system: &#xA;  # same as blocks before with flake-utils.lib.eachDefaultSystem&#xA;)&#xA;&#xA;With all of it put together below, you can see how to use with let .. in expression:&#xA;&#xA;{&#xA;  description = &#34;Development environment&#34;;&#xA;&#xA;  inputs = {&#xA;    nixpkgs = { url = &#34;github:NixOS/nixpkgs/nixpkgs-unstable&#34;; };&#xA;    flake-utils = { url = &#34;github:numtide/flake-utils&#34;; };&#xA;  };&#xA;&#xA;  outputs = { self, nixpkgs, flake-utils }:&#xA;    let supportedSystems = [ &#34;x8664-linux&#34; &#34;aarch64-linux&#34; ];&#xA;    in&#xA;    flake-utils.lib.eachSystem supportedSystems (system:&#xA;      let&#xA;        inherit (nixpkgs.lib) optional;&#xA;        pkgs = import nixpkgs { inherit system; };&#xA;&#xA;        elixir = pkgs.beam.packages.erlang.elixir;&#xA;        elixir-ls = pkgs.beam.packages.erlang.elixirls;&#xA;        locales = pkgs.glibcLocales;&#xA;      in&#xA;      {&#xA;        devShell = pkgs.mkShell&#xA;          {&#xA;            buildInputs = [&#xA;              elixir&#xA;              locales&#xA;            ];&#xA;          };&#xA;      }&#xA;    );&#xA;}&#xA; &#xA;Now when you run nix flake show the output should be only the platforms you defined: &#xA;&#xA;    ├───aarch64-linux: development environment &#39;nix-shell&#39;&#xA;    └───x86_64-linux: development environment &#39;nix-shell&#39;&#xA;`]]&gt;</description>
      <content:encoded><![CDATA[<p>Never is a project started from &#39;just&#39; the init. You have to take care of packages you use, CI tools for builds you make, database hookups, development tooling, and countless other parts. All of this takes time. With nix flakes, you may be able to start with all the main components you need immediately. Giving way to actually developing that app you been itching to build, without the days/weeks adventure getting everything you need just right.</p>



<p>Now it doesn&#39;t mean that immediately reading this starter guide, you will have everything under the sun set up with <a href="https://nixos.org/manual/nix/unstable/command-ref/new-cli/nix3-flake.html">Nix Flakes</a> for your development need. But at least, you won&#39;t have to worry about setting up asdf, your weird hacks you need for your machine and the other tiny little things to get elixir started with <a href="https://github.com/elixir-lsp/elixir-ls">elixir-ls</a>.</p>

<h2 id="background" id="background">Background</h2>

<p>A little background. <a href="https://nixos.org/manual/nix/stable/introduction.html">Nix</a>, for the uninitiated, is a purely functional language for package management. What makes Nix interesting is you can use the purely functional aspect to build out artifacts which are entirely idempotent. Meaning, no matter how many times you run the nix expressions you have, the end result will always be the same regardless of external state of a machine. Its build structure as a package manager has evolved the language to build out guaranteed result for all sorts of software. Yet, Nix itself isn&#39;t exactly easy to learn. Due in part to the ambitions of the project and difficulties, which arose from those ambitions, complexities crept in. <a href="https://nixos.org/manual/nix/unstable/command-ref/new-cli/nix3-flake.html">Nix Flakes</a> is an answer to some of these complexities and more.</p>

<p>With Nix Flakes, you have the ability to have a very well defined package for a project. Written and using Nix in a way that takes all the learning from its years. Making it easier to define what you want from nix; the build result for a package.</p>

<h2 id="getting-flakes-enabled" id="getting-flakes-enabled">Getting flakes enabled</h2>

<p>So how do you get started with Nix Flakes? The first part that you should probably have is <a href="https://nixos.org/guides/install-nix.html">nix already installed</a> and some <a href="https://nixos.wiki/wiki/Nix_Expression_Language">familiarity with the nix language</a>. Run through the guide to your platform needs.</p>

<p>Next is the settings to use nix with flakes. Since flakes is still in development (but relatively stable), you do need to enable the feature on nix. You can do so by enabling the experimental features of both <code>nix-command</code> and <code>flakes</code> through the <a href="https://nixos.org/manual/nix/unstable/command-ref/conf-file.html">nix.conf file</a>.</p>

<pre><code># make nix config path if not existent
mkdir ~/.config/nix/ -p
# add the settings to the file on config path
echo &#34;experimental-features = nix-command flakes&#34; | tee ~/.config/nix/nix.conf -a
</code></pre>

<p>Once the settings are applied, you should be able to validate by running <code>show-config</code></p>

<pre><code>nix show-config | grep experimental
</code></pre>

<p>If all is successful, you should see an output for the same experimental features you wrote on the <code>nix.conf</code> file.</p>

<h2 id="the-flake-nix-file" id="the-flake-nix-file">The <code>flake.nix</code> file</h2>

<p>So now to get to use nix flake comes the heart of the project, the <code>flake.nix</code> file. The file itself needs three defined keys on the set, <code>description</code>, <code>inputs</code>, and <code>outputs</code>. Each one of them plays a different role in how to define your package you want to build.</p>

<h3 id="description-key" id="description-key"><code>description</code> key</h3>

<p>The <code>description</code> key is a one liner description of what the flake project is. Helps in giving what the flake is for quick review after you have a few hundred of these built out...</p>

<pre><code># flake.nix, ignoring input and output
{
  description = &#34;A description of some kind&#34;;
}
</code></pre>

<h3 id="inputs-key" id="inputs-key"><code>inputs</code> key</h3>

<p>The <code>inputs</code> is how you can import external sources of other flakes into the flake project you have. In other words, any project you may need or tools required to get started, this is where you will define their source.  Example below is using the standard nixpkgs and a tool called <a href="https://github.com/numtide/flake-utils">flake-utils</a>, which provides a set of functions to make flake nix packages simpler to set up without external dependencies.</p>

<pre><code class="language-nix"># flake.nix, ignoring description and output
{
  inputs = {
    # using unstable branch for the latest packages of nixpkgs
    nixpkgs = { url = &#34;github:NixOS/nixpkgs/nixpkgs-unstable&#34;; }; 
    flake-utils = { url = &#34;github:numtide/flake-utils&#34;; };
  };
}
</code></pre>

<h3 id="outputs-key" id="outputs-key"><code>outputs</code> key</h3>

<p>The <code>outputs</code> is where the bulk of your logic for what you will build with flakes. It has quite <a href="https://nixos.wiki/wiki/Flakes#Output_schema">a numerous of options</a>, but in this use case, we only care of the <code>devShell</code> key, which is what will be used to populate the development environment. While the following may all look like a lot for the output, it&#39;s also everything that would be needed for either building out a nix flake package or use for a development environment:</p>

<pre><code class="language-nix"># flake.nix, ignoreing description and input
{
  outputs = { self, nixpkgs, flake-utils }:
   flake-utils.lib.eachDefaultSystem (system:
      let
        pkgs = import nixpkgs { inherit system; };

        elixir = pkgs.beam.packages.erlang.elixir;
        elixir-ls = pkgs.beam.packages.erlang.elixir_ls;
        locales = pkgs.glibcLocales;
      in
      {
        devShell = pkgs.mkShell {
          buildInputs = [
            elixir
            locales
          ]
        }
      });
}
</code></pre>

<p>The output key is actually a function which takes the inputs defined on inputs. Hence the set <code>{ self, nixpkgs, flake-utils }</code>. All the inputs on the function were defined on the input with <code>self</code> being the <code>flake.nix</code> file itself. The next portion is using a simple but powerful flake-utils function called <code>eachDefaultSystem</code>. What the function provides is actually build the development environment for all available platforms currently available for nix as a default. You can see the list by running <code>nix flake show</code>  (after the file is fully written) and you will be provided an output like the following:</p>

<pre><code>└───devShell
    ├───aarch64-darwin: development environment &#39;nix-shell&#39;
    ├───aarch64-linux: development environment &#39;nix-shell&#39;
    ├───i686-linux: development environment &#39;nix-shell&#39;
    ├───x86_64-darwin: development environment &#39;nix-shell&#39;
    └───x86_64-linux: development environment &#39;nix-shell&#39;
</code></pre>

<p>Meaning, you do not have to worry about what OS you using, as long as it&#39;s linux or macos. You write your nix flake with the ability to use it with all the supported platforms from the start for your environment. In other words, Write once, run on all the machines. No more of that &#39;it runs on my machine&#39; debacle.</p>

<p>The last section is a <code>let .. in</code> pair to both declare what you will use in the system you are building and <code>devShell</code> itself. The packages use here are simply elixir, <a href="https://github.com/elixir-lsp/elixir-ls">elixir-ls</a> for sanity, and locales to make sure elixir is able to use the proper locale settings of the shell environment produced by the nix flake.</p>

<p>Finally, the <code>devShell</code> in this case is the buildInputs wanted for the shell environment and nothing more:</p>

<pre><code class="language-nix">      {
        devShell = pkgs.mkShell {
          buildInputs = [
            elixir
            locales
          ]
      }
</code></pre>

<p>Notice while <code>elixir-ls</code> package isn&#39;t directly declared in the mkShell buildInput, it is part of the output on <code>let</code>. Allowing to still have linked access to its packages.</p>

<h2 id="the-full-nix-flake-development-environment" id="the-full-nix-flake-development-environment">The full nix flake development environment</h2>

<p>Finally, putting it all together, getting a nix flake project started for your development environment, all falls down to what packages you need on the <code>devShell</code>.  With the <code>flake.nix</code> file on the root of your project, you have the ability to have the packages you need and ready for you to work with.</p>

<pre><code class="language-nix">{
    description = &#34;Development environment&#34;;

  inputs = {
      nixpkgs = { url = &#34;github:NixOS/nixpkgs/nixpkgs-unstable&#34;; };
    flake-utils = { url = &#34;github:numtide/flake-utils&#34;; };
  };

  outputs = { self, nixpkgs, flake-utils }:
    flake-utils.lib.eachDefaultSystem (system:
      let
        inherit (nixpkgs.lib) optional;
        pkgs = import nixpkgs { inherit system; };

        elixir = pkgs.beam.packages.erlang.elixir;
        elixir-ls = pkgs.beam.packages.erlang.elixir_ls;
        locales = pkgs.glibcLocales;
      in
      {
          devShell = pkgs.mkShell
          {
              buildInputs = [
                elixir
              locales
            ];
          };
      }
    );
}
</code></pre>

<p><a href="https://baez.link/tag:elixir" class="hashtag"><span>#</span><span class="p-category">elixir</span></a> <a href="https://baez.link/tag:development" class="hashtag"><span>#</span><span class="p-category">development</span></a> <a href="https://baez.link/tag:nix" class="hashtag"><span>#</span><span class="p-category">nix</span></a> <a href="https://baez.link/tag:flakes" class="hashtag"><span>#</span><span class="p-category">flakes</span></a></p>

<h2 id="bonus-limiting-by-platform-of-choice-your-nix-flake" id="bonus-limiting-by-platform-of-choice-your-nix-flake">Bonus: Limiting by platform of choice your nix flake</h2>

<p>Glad you made it this far. Well friend, besides the default system listing on <code>flake-utils</code>, there is another route you can go in setting up you development environment. You may have noticed that the run of <code>nix flake show</code> showed multiple platforms and architectures available by default. However, it may be you don&#39;t need to use all those platforms and architecture. You may like to target only a set of architectures or platforms you need and that&#39;s it. Speeding up your build process and saving on some storage if you don&#39;t want those extra platforms.</p>

<p><a href="https://github.com/numtide/flake-utils">flake-utils</a> has the option with the <code>flake-utils.lib.eachSystem</code> function. The function itself takes an array of systems you want the flake to build out. To use the function, you have to use a <code>let .. in</code> expression to define the array and then it&#39;s use. For my use, I only target <code>aarch64-linux</code> and <code>x86_64-linux</code> since those are the only platforms I work with. So I define them on an array.</p>

<pre><code class="language-nix">supportedSystems = [ &#34;x86_64-linux&#34; &#34;aarch64-linux&#34; ];
</code></pre>

<p>Then I use that defined array to apply to the the function <code>flake-utils.lib.eachSystem</code></p>

<pre><code>flake-utils.lib.eachSystem supportedSystems (system: 
  # same as blocks before with `flake-utils.lib.eachDefaultSystem`
)
</code></pre>

<p>With all of it put together below, you can see how to use with <code>let .. in</code> expression:</p>

<pre><code class="language-nix">{
  description = &#34;Development environment&#34;;

  inputs = {
    nixpkgs = { url = &#34;github:NixOS/nixpkgs/nixpkgs-unstable&#34;; };
    flake-utils = { url = &#34;github:numtide/flake-utils&#34;; };
  };

  outputs = { self, nixpkgs, flake-utils }:
    let supportedSystems = [ &#34;x86_64-linux&#34; &#34;aarch64-linux&#34; ];
    in
    flake-utils.lib.eachSystem supportedSystems (system:
      let
        inherit (nixpkgs.lib) optional;
        pkgs = import nixpkgs { inherit system; };

        elixir = pkgs.beam.packages.erlang.elixir;
        elixir-ls = pkgs.beam.packages.erlang.elixir_ls;
        locales = pkgs.glibcLocales;
      in
      {
        devShell = pkgs.mkShell
          {
            buildInputs = [
              elixir
              locales
            ];
          };
      }
    );
}
</code></pre>

<p>Now when you run <code>nix flake show</code> the output should be only the platforms you defined:</p>

<pre><code>    ├───aarch64-linux: development environment &#39;nix-shell&#39;
    └───x86_64-linux: development environment &#39;nix-shell&#39;
</code></pre>
]]></content:encoded>
      <guid>https://baez.link/getting-started-using-nix-flakes-as-an-elixir-development-environment</guid>
      <pubDate>Sun, 09 Jan 2022 18:12:28 +0000</pubDate>
    </item>
  </channel>
</rss>