Setup per project scientific development environments with ease, without dependency conflicts or messing up your global environment, all while preserving whatever sanity you have left!
- Python - Manage with uv and use marimo for reactive notebooks.
- Julia - Use Pluto for a plug-and-play notebook experience.
- Typst - A modern typesetting language with instant preview and intuitive syntax.
- Straightforward to extend to a new language, with modules. (PRs are welcome!)
-
Install Nix if you don't have it already. Make sure to enable flakes support by adding the following to
~/.config/nix/nix.confor/etc/nix/nix.conf:experimental-features = nix-command flakesYou can learn more about Nix language here (by having a quick glance at the table) or here (for a more informative guide).
-
To start using the default template:
nix flake init -t github:Vortriz/scientific-env#default
-
You can run any given command to enter the respective environment:
- Python
nix develop .#python -c setup - Julia
nix develop .#julia -c update-registry - Typst
nix develop .#typst - Default
nix develop
(Optional) You can also install direnv to auto-load the project environment whenever you enter the directory. Then just
direnv allowthe project. - Python
- Delete
nix/<some-language>folder if you absolutely don't want to use that language in your project. - (Optional) If you use direnv, you can change the default environment to auto-load by modifying the
use flake .#defaultline in the.envrcfile.- e.g. change to
use flake .#pythonto auto-load Python environment
- e.g. change to
- The default environment is blank. You can modify the
nix/default/shell.nixfile to configure it to your liking. Refer to devshell docs for more information or look atnix/<language>/shell.nixfiles for examples.
Note
If you are not using direnv, make sure to run nix develop after every change in any file inside nix/ folder. (or just start using direnv :P)
The following command enters Python environment, sets up uv and marimo:
nix develop .#python -c setupCreate a new notebook with:
uv run marimo newThe package management inside marimo is also handled via uv, so that your pyproject.toml is automatically updated when you add packages in the notebook. Neat, huh?
For subsequent runs, you can just run:
uv run marimo editFor more information on using marimo, try the interactive tutorials by running:
uv run marimo tutorial --helpor visit the quickstart guide here.
Marimo will automatically format the notebooks with Ruff. Configure the formatters by modifying nix/python/fmt.nix. A git pre-commit-hook will handle auto-formatting. You can also manually run the formatter with:
nix fmtThe following command enters Julia environment and updates the package registry:
nix develop .#julia -c update-registryThis will take a significant time to run, since Julia is precompiling the necessary packages.
Add your Julia dependencies to nix/julia/_package.nix and re-run nix develop .#julia. After that, create and launch a new Pluto notebook with:
create <some-name>.jlFor subsequent runs, you can just run:
plutoJulia code is formatted using JuliaFormatter.jl. Configure it by modifying nix/julia/fmt.jl. A git pre-commit-hook will handle auto-formatting. You can also manually run the formatter with:
nix fmtEnter Typst environment with:
nix develop .#typstThen you can use Typst normally.
Typst code is formatted using typstyle. Configure it by modifying nix/typst/fmt.nix. A git pre-commit-hook will handle auto-formatting. You can also manually run the formatter with:
nix fmtThis is a very opinionated setup that I have refined over a long time to strike a balance between the reproducibility of pure Nix environments and convenience of language specific tools. This lets me focus on the work rather than deal with ✨ Dependency Hell ✨.
If you were to purely use Nix for managing all dependencies, you would never have to hear "but it works on my machine"! But it has its own cost:
- Nixpkgs (the package repository for nix) does not contain all the packages that would be present on, lets say PyPI. Neither does it contain all released versions of them.
- It can be an absolute pain to package something for Nix, which is especially frustrating when you just want to get something done.
So, we trade some of the "purity" of Nix for sanity. The aim is to enforce the use of better tools and practices that drive you towards a more reproducible environment.
- For Python, we use uv, which creates a lockfile (just like Nix) to ensure reproducibility. We also use marimo notebooks instead of Jupyter, because Jupyter is simply not made for reproducibility.
- Pluto is to Julia what marimo is to Python - a better notebook experience that is reactive and reproducible.
Added benefit of this template is that you can have multiple languages in the same project and toggle them at will.
Change the Python version in nix/python/shell.nix file. For example, to use Python 3.11, change pythonPkg value to pkgs.python311. Nixpkgs supports Python versions from 3.10 to 3.14.
Refer to the devshell docs for more information on how to configure the shell.nix files. You can also look at the existing nix/<language>/shell.nix files for examples. Feel free to open a Github discussion or hit me up on Matrix.