Speed Up Development with Nix Devshells
It shouldn’t take a whole day to setup a repo you haven’t worked on before. It shouln’t be that hard to pick up a project you stopped working on 6 months ago because your priorities changed. Enter development shells from nix.
If you haven’t heard about nix before, the tldr is that Nix is a configuration system that solves the problem “It works on my machine”. It is notoriously difficult to get into, but it is very rewarding once you do.
In this post, we will take a look at how we can setup a reproducible development shell with minimal setup so that we can start coding faster!
Table of contents
Open Table of contents
Prerequisites
- Nix installed
- Flakes enabled For nix
Run the following command to install nix:
- Install nix
sh <(curl -L https://nixos.org/nix/install)
Flakes are feature of nix that is considered experimental but has been around for many years. Therefore we need to enable this feature.
- Enable flakes
mkdir -p ~/.config/nix && echo "experimental-features = nix-command flakes " >> ~/.config/nix/nix.conf
What’s a flake?
A nix flake is convenient packaging interface for some nix code typically distributed through a remote repository like github. A flake is basically a software package.
Ready, Set, Go
To create a nix devshell run the following command
nix flake new -t "github:numtide/devshell" project/
This will generate some boilerplate code for getting started with a nix shell
❯ ls -lha
Permissions Size User Date Modified Name
.rw-r--r-- 294 kog 27 sep 12:06 .envrc
.rw-r--r-- 9 kog 27 sep 12:06 .gitignore
.rw-r--r-- 68 kog 27 sep 12:06 devshell.toml
.rw-r--r-- 2,8k kog 27 sep 12:06 flake.lock
.rw-r--r-- 673 kog 27 sep 12:06 flake.nix
.rw-r--r-- 606 kog 27 sep 12:06 shell.nix
Now, simply run
nix develop
And you get this output
❯ nix develop
warning: updating lock file '/home/kog/repos/coding/demos/project/flake.lock':
• Added input 'flake-compat':
'github:edolstra/flake-compat/0f9255e01c2351cc7d116c072cb317785dd33b33' (2023-10-04)
🔨 Welcome to devshell
[general commands]
hello - A program that produces a familiar, friendly greeting
menu - prints this menu
[devshell]$
And we are done!
What just happened?
When we ran nix flake new -t github:numtide/devshell
we created a nix flake template.
flake.nix
file contained a list of what nix code it should run.flake.lock
file contains metadata about the code, i.e what version of the repository is should pull. The lock file ensures reproducability. Similar topackage-lock.json
in nodejs orpoetry.lock
in python.- The template also created
devshell.toml
which will be our interface for creating our devshell.
Speed is key
We now have a good template to start from. Let’s make a hello world fast python application!
First, lets add the nixpkgs package repository to the flake
{
description = "virtual environments";
inputs.nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable"; # <--- Add this line
inputs.devshell.url = "github:numtide/devshell";
inputs.flake-utils.url = "github:numtide/flake-utils";
inputs.flake-compat = {
url = "github:edolstra/flake-compat";
flake = false;
};
outputs = {
self,
flake-utils,
devshell,
nixpkgs,
...
}:
flake-utils.lib.eachDefaultSystem (system: {
devShells.default = let
pkgs = import nixpkgs {
inherit system;
overlays = [devshell.overlays.default];
};
in
pkgs.devshell.mkShell {imports = [(pkgs.devshell.importTOML ./devshell.toml)];};
});
}
Then run nix flake update
to commit dependency on nixpkgs
.
Now we edit devshell.toml
to look like this
# https://numtide.github.io/devshell
[[commands]]
help = "Install dependencies"
name = "install"
command = "poetry install"
[[commands]]
help = " ⚡⚡⚡Super Fast Devshells with python!"
name = "faster"
command = "install && poetry run uvicorn fast:app"
[[commands]]
package = "poetry"
[devshell]
packages = [
"poetry"
]
Then add pyproject.toml
[tool.poetry]
name = "fast-devshell"
version = "0.1.0"
description = ""
authors = ["Alexander Reinthal <email@reinthal.me>"]
readme = "README.md"
[tool.poetry.dependencies]
python = "^3.10"
fastapi = "^0.115.0"
uvicorn = "^0.30.6"
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
then add the python application:
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
async def read_root():
return {"message": "So fast!⚡⚡⚡"}
then, re-enter your shell nix develop
if necessary. Finally, run our python app:
faster
project2 on main is 📦 v0.1.0 via 🐍 via ❄️ impure (devshell-env) took 8s
❯ faster
Creating virtualenv fast-devshell-GWUqcQw5-py3.12 in /home/kog/.cache/pypoetry/virtualenvs
Installing dependencies from lock file
Package operations: 12 installs, 0 updates, 0 removals
- Installing idna (3.10)
- Installing sniffio (1.3.1)
- Installing typing-extensions (4.12.2)
- Installing annotated-types (0.7.0)
- Installing anyio (4.6.0)
- Installing pydantic-core (2.23.4)
- Installing click (8.1.7)
- Installing h11 (0.14.0)
- Installing pydantic (2.9.2)
- Installing starlette (0.38.6)
- Installing fastapi (0.115.0)
- Installing uvicorn (0.30.6)
INFO: Started server process [306430]
INFO: Waiting for application startup.
INFO: Application startup complete.
INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
INFO: 127.0.0.1:50032 - "GET / HTTP/1.1" 200 OK
INFO: 127.0.0.1:50032 - "GET /favicon.ico HTTP/1.1" 404 Not Found
Discussion: Why not just use poetry without nix?
Good question! Nix devshells are general and we can setup any environment we like this way, go, rust, c++, zig, ruby. As long as necessary dependencies exist in nixpkgs, which is the worlds largest package repository.
Setting up devshells can be done in many different ways, each programming language usually have their own package pmanager with it’s own quirks. Nix strength lies it being a general. For example, when a project is using multiple programming language, like for example, javascript for the frontend and java for the backend.
Summary and next steps
In this post we setup a fast development shell using nix flakes. We can add lots of functionality to the devshell.toml
. We can add environment variables, git-hooks, even daemon services like a postgres database or redis for caching.
For more info on how this particular devshell works check out Numtide Devshell documentation.
I hope you learned something!
Please connect with me on socials if you wanna talk about code! :) See links below this article