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:
- You have NixOS installed
- You have direnv included in your
systemPackages
(optional) - Nix Flakes are enabled. (https://nixos.wiki/wiki/Flakes)
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:
{
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.
.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.