Skip to main content

Elixir Development in NixOS

Simplify Elixir development using Nix Shells. Project specific development environments without VMs, containers, or headaches.

· By Joe Bellus · 4 min read

Fighting with tooling versions, and dependencies sucks. There are solutions to it like running specific databases via Docker containers, or using dev shells in VSCode (sad panda), or per project environment VMs.

Alternatively, you could just cd project and have the entire environment there.

You'll be cradled in a Nix Shell, a magical pocket dimension where all the necessary tools are automatically summoned: Erlang, Elixir, NodeJS, Postgres, and everything needed for their language servers, linting, and code formatting. All with specific versions tailored for a single project.

The best part? Absolutely nothing gets permanently installed on your system. This is pure wizardry contained within the project directory – all these powerful tools vanish instantly when you exit or leave the folder (cd ..). It's like having a portable, self-contained development universe.

Requirements

To get started we will assume a few things:

What we will get

When we are finished, you should have a fully functional development setup that automatically loads when entering the path. It will include Erlang, Elixir, LSP tools, NodeJS, LSP/Linting/Formatting, a local Postgres server.

As with any Nix environment this will be a Nix Shell and wont permanently install anything to the system. All tooling will be available when inside the project path and wont persist when leaving it.

System packages

Package Description
erlang Erlang VM and tooling
elixir Latest version of Elixir
lexical Elixir LSP
nodejs_22 NodeJS interpreter
inotify-tools File system events
fswatch File system events

NPM packages

Package Description
pnpm NPM alternative
vscode-languageservers-extracted LSP for javascript
prettier Formatting for Javascript and CSS/SCSS

Database

A localized version of Postgres will run directly from the project directory, allowing Phoenix apps to use a specific Postgres version without needing Postgres installed on the system or using Docker containers.

The setup

Let’s start a new Phoenix project! There’s a slight chicken-and-egg problem here— we want to set up our project folder with mix phx.new, but Elixir isn’t installed. Nix shell comes to the rescue. We can use an ephemeral Nix shell with Elixir to bootstrap our project. Follow these commands to create a new Nix shell, install Elixir (including confirmation steps), and set up your Phoenix project.

nix-shell -p elixir
mix archive.install hex phx_new
mix phx.new myproject

We don't need to do any setup now, just exit the nix shell with exit.

The flake.nix

Create a new file inside the project called myproject/flake.nix with the following contents:

💡
Change the variable project_name to the name of your project. The database generated will be project_name_dev
{
  description = "Basic Elixir Application";

  inputs = { nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable"; };

  outputs = { self, nixpkgs }:
    let
      system = "x86_64-linux";
      project_name="myproject";
      pkgs = import nixpkgs { inherit system; };
      erlang = pkgs.beam.packages.erlang_26.erlang;
      elixir = pkgs.beam.packages.erlang_26.elixir;
    in {
      devShells.${system}.default = pkgs.mkShell {
        packages = [
          pkgs.fswatch
          erlang
          elixir
          pkgs.inotify-tools
          pkgs.lexical
          pkgs.postgresql
          pkgs.nodejs_22
          pkgs.nodePackages.vscode-langservers-extracted
          pkgs.nodePackages.pnpm
          pkgs.nodePackages.prettier
        ];
	       shellHook = ''
          export PG=$PWD/.dev_postgres
          export PGDATA=$PG/data
          export PGPORT=5432
          export PGHOST=localhost
          export PGUSER=$USER
          export LANG="en_US.UTF-8"
          export ERL_AFLAGS="-kernel shell_history enabled"
          alias pg_setup="\
        	  initdb -D $PGDATA &&
    		  echo \"unix_socket_directories = \
                '$PGDATA'\" >> $PGDATA/postgresql.conf &&\
		    pg_start &&\
		    createdb &&\
		    mix ecto.create &&\
		    mix ecto.migrate"
          alias pg_stop='pg_ctl -D $PGDATA stop'
          alias pg_start='pg_ctl -D $PGDATA -l $PG/postgres.log start'
        '';
      };
    };
}

flake.nix

Update the Repo config

We need to make a small modification the development and test configurations for the Phoenix app. Change the Repo configuration in config/dev.exs and config/test.exs:

config :myproject, Myproject.Repo,
  username: System.get_env("USER"),
  password: "postgres",
  socket_dir: System.get_env("PGDATA"),
  database: "myproject_dev",
  stacktrace: true,
  show_sensitive_data_on_connection_error: true,
  pool_size: 11

config/dev.exs

config :myproject, Myproject.Repo,
  username: System.get_env("USER"),
  password: "postgres",
  socket_dir: System.get_env("PGDATA"),
  database: "myproject_test",
  stacktrace: true,
  show_sensitive_data_on_connection_error: true,
  pool_size: 11

config/test.exs

Finish setting up the app

Now we can enter the environment with nix develop. After the environment initializes all our tooling is available. We do the initial database setup with pg_setup and launch the Phoenix server.

nix develop
mix deps.get
pg_setup
mix ecto.migrate
mix phx.server

Moving forward: To develop the application, navigate to its directory. Then run:

  • nix develop - This enters the configured development environment.
  • pg_start - This starts the local Postgres instance required for Phoenix.

When finished, you can stop the Postgres server with pg_stop and exit the Nix shell with exit. Tada! The environment cleans up behind itself.

💡
Remember to add .dev_postgres to .gitignore for your project. This path holds the postgres files and configuration.

Setting up DIRENV

With DIRENV the nix shell will be loaded as soon as we enter the path. No more need to type nix develop. Many IDEs/editors can also take advantage of DIRENV to load the shell automatically when the project is opened. Emacs and Neovim can both do this.

To setup DIRENV create a .envrc file in the project root with the following line:

use flake

.envrc

Enable it explicitly:

direnv allow

Now when you cd into the path the environment will be automatically loaded, when you leave the project path it will be unloaded. You still need to execute pg_start and pg_start manually however.

Updated on May 31, 2025