Devenv is an environment management utility for NixOS. It makes setting up and managing development mostly painless.
Phoenix applications have a number of moving parts that need to be available for everything to work nicely. Heres what we will setup:
- Elixir
- Erlang/OTP
- NodeJS
- NPM
- ElixirLS
- PostgreSQL
- Process management for bringing up the entire system
- Convenience commands for various mix tasks.
Devenv Features
- Easily setup basic language tooling
- Configured with Nix and allows for the complex customization one would expect
- Easily add dependent services
- Launch and control multiple processes in a multiplexer
- Define custom tasks
Installing Devenv
The easiest way to install devenv is to add it to the packages list inside the Nix configuration.
packages = with pkgs; [
devenv
];
Setting Up a Project
I don't keep Elixir installed globally in my system so to setup a new project i use a nix-shell
and create a new project via mix
.
nix-shell -p elixir
mix phx.new test_app
cd test_app
mix deps.get
mix compile
exit
This will give us a new Phoenix application. After running exit
the nix-shell
will be closed and we will no longer have mix
in path.
Setting Up Devenv
To setup the initial devenv files we can use the init
command to generate the files we need.
devenv init
direnv allow
This will give us a number of files:
- devenv.nix - This is where we will define out environment
- devenv.yml - This is a configuration file for setting up the package manager, we can leave this file at its default.
- devenv.lock - This is a lock file for the package manager. Generally we don't have any need to modify it manually.
- .envrc - This sets up direnv so the environment will be automatically loaded when the path is accessed.
- .devenv- This directory contains the state and supporting files for the environment
- .direnv - This directory is setup by direnv
- .devenv.flake.nix - This is the generated flake file used for the environment. We don't need to worry about editing it manually.
- .gitignore - A gitignore file is generated to ignore the .devenv path since that shouldn't be committed to source control.
The only file we really need to be concerned with is devenv.nix
.
The Devenv.nix File
{
pkgs,
lib,
config,
...
}:
{
languages.elixir.enable = true;
languages.javascript.enable = true;
languages.javascript.pnpm.enable = true;
tasks = {
"clean:lsp" = {
exec = ''
rm -rf .elixir_ls
rm -rf .elixir-tools
rm -rf .lexical
rm -Rf deps
mix deps.get
mix clean
mix compile
'';
};
};
packages = [
pkgs.git
pkgs.expect
pkgs.nodePackages.vscode-langservers-extracted
pkgs.nodePackages.prettier
]
++ lib.optionals pkgs.stdenv.isLinux [ pkgs.inotify-tools ];
services.postgres = {
enable = true;
listen_addresses = "localhost";
initialScript = "CREATE ROLE postgres WITH LOGIN PASSWORD 'postgres' SUPERUSER;";
initialDatabases = [
{ name = "test_app_dev"; }
{ name = "test_app_test"; }
];
};
processes.phoenix.exec = "unbuffer mix phx.server";
git-hooks.hooks = {
mix-format.enable = true;
};
}
In this environment we are installing both the Elixir tooling and the NodeJS tooling. Since a Phoenix app will commonly use npm
and have some JavaScript to deal with.
The tasks
block allows us to define convenience tasks. Here we have a simple task for cleaning up problems that occur with ElixirLS. It will delete the LSP folders, delete dependencies, redownload dependencies, and recompile.
Next we have our package list. The things we want to install in this environment, outside of the basic language tooling:
- Git - This is probably installed system wide, but just incase
- Expect - Expect is used to give us the unbuffer command. This command is an easy way to establish a psudo terminal and get colored output back.
- The LSP tooling for JavaScript
- Prettier - Code formatting for JavaScript/CSS
- inotifytools - For file change notification
Postgres is also setup and its data folder is sequestered in the .devenv path for us. So it is kept with the project. No need for sharing a local postgres database between projects, using docker containers, or having a non local dev database.
Some things to note here:
- You can change the username and password for the postgres user if desired. Change it in the
initialScript
property. - The
initialDatabases
should be changed to reflect the application name and thus the database names expected in yourdev.exs
andtest.exs
.
The process
deceleration lets us define a process to run when we launch the development instance. In this case we are defining a process for executing the Phoenix server.
The last thing is the automatic creation of a GitHook to run mix format before commits.
Using the Environment
With direnv
enabled, using cd
to enter the directory will automatically load the environment. Mix, npm, prettier, etc will all be available in path. When leaving the path they will all be removed. With this setup you don't need to worry about tooling, they should just always be there. Direnv will also work with most editors. Emacs has a direnv package that will automatically load the environment when opening the project, Nvim obviously will work when executed from inside the path, and VSCode also has a direnv package.
To run the development version of the application all we need is the devenv up
command.
devenv up
This will run all associated processes and services included in the config. For this example: It will run the Phoenix server and the postgres server. Both servers are run inside Process Compose, by default, allowing the processes to be stopped, started, and restarted. Each process will also output separately in a multiplexer style interface.