Getting Started Using Nix Flakes As An Elixir Development Environment
Tags: elixir, nix, flakes, development
Published: 2022-01-09 18:39:26

---
title: Getting Started Using Nix Flakes As An Elixir Development Environment
published: true
date: 2022-01-09 18:12:28 UTC
tags: elixir, nix, flakes, development
canonical_url: https://baez.link/getting-started-using-nix-flakes-as-an-elixir-development-environment?pk_campaign=rss-feed
---

Never is a project started from 'just' 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.

Now it doesn't mean that immediately reading this starter guide, you will have everything under the sun set up with [Nix Flakes](https://nixos.org/manual/nix/unstable/command-ref/new-cli/nix3-flake.html) for your development need. But at least, you won'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](https://github.com/elixir-lsp/elixir-ls).

## Background

A little background. [Nix](https://nixos.org/manual/nix/stable/introduction.html), 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'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](https://nixos.org/manual/nix/unstable/command-ref/new-cli/nix3-flake.html) is an answer to some of these complexities and more.

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.

## Getting flakes enabled

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

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](https://nixos.org/manual/nix/unstable/command-ref/conf-file.html).

```
# make nix config path if not existent
mkdir ~/.config/nix/ -p
# add the settings to the file on config path
echo "experimental-features = nix-command flakes" | tee ~/.config/nix/nix.conf -a

```

Once the settings are applied, you should be able to validate by running `show-config`

```
nix show-config | grep experimental

```

If all is successful, you should see an output for the same experimental features you wrote on the `nix.conf` file.

## The `flake.nix` file

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.

### `description` key

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

``` nix
# flake.nix, ignoring input and output
{
  description = "A description of some kind";
}

```

### `inputs` key

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](https://github.com/numtide/flake-utils), which provides a set of functions to make flake nix packages simpler to set up without external dependencies.

``` nix
# flake.nix, ignoring description and output
{
  inputs = {
    # using unstable branch for the latest packages of nixpkgs
    nixpkgs = { url = "github:NixOS/nixpkgs/nixpkgs-unstable"; }; 
    flake-utils = { url = "github:numtide/flake-utils"; };
  };
}

```

### `outputs` key

The `outputs` is where the bulk of your logic for what you will build with flakes. It has quite [a numerous of options](https://nixos.wiki/wiki/Flakes#Output_schema), 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's also everything that would be needed for either building out a nix flake package or use for a development environment:

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

```

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:

```
└───devShell
    ├───aarch64-darwin: development environment 'nix-shell'
    ├───aarch64-linux: development environment 'nix-shell'
    ├───i686-linux: development environment 'nix-shell'
    ├───x86_64-darwin: development environment 'nix-shell'
    └───x86_64-linux: development environment 'nix-shell'

```

Meaning, you do not have to worry about what OS you using, as long as it'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 'it runs on my machine' debacle.

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](https://github.com/elixir-lsp/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.

Finally, the `devShell` in this case is the buildInputs wanted for the shell environment and nothing more:

``` nix
      {
        devShell = pkgs.mkShell {
          buildInputs = [
            elixir
            locales
          ]
      }

```

Notice while `elixir-ls` package isn't directly declared in the mkShell buildInput, it is part of the output on `let`. Allowing to still have linked access to its packages.

## The full nix flake development environment

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.

``` nix
{
  description = "Development environment";

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

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

```

[#elixir](https://baez.link/tag:elixir) [#development](https://baez.link/tag:development) [#nix](https://baez.link/tag:nix) [#flakes](https://baez.link/tag:flakes)

## Bonus: Limiting by platform of choice your nix flake

[Read more...](https://baez.link/getting-started-using-nix-flakes-as-an-elixir-development-environment)