I've been part of the Nix community for more than 10 years and in the last 4 years focused on making it documented, simple and accessible for any developer.
After building Cachix (where you can store any software binaries with a few steps) we realized that there needs to be an intuitive interface for crafting developer environments.
I'm really looking forward what you build on top of devenv. We're only beginning to explore the area of what's possible, so please give as much feedback as you have.
The two things that stand out that maybe look slightly easier are the processes section, and the pre-commit hooks sections - but from what I can tell these are generally all solveable via native flakes.
Just curious - if this is a valuable tool to add to the arsenal, I'm all for it, but I'm curious what the strong selling points are here over what is already available.
The main benefit is abstraction using modules: this allows you to extend, compose and abstract your environment. Processes and pre-commit hooks are only the beginning of what's possible.
There's also higher-level CLI interface for building images, sharing environments, etc.
Note that this is only scratching the design surface of what's possible.
I've been mentioning to a work colleague that someone needs to do for the somewhat baroque Nix tools and concepts what Docker did for containers.
It just needs a nice porcelain over the top that provides some consistent CLI UX, but lets you dive down deeper as you get more comfortable with the system.
As someone who just two weeks ago got into Nix/NixOS via this repo[1] from mitchellh, one thing I'd love:
A nice, seamless way to remote connect JetBrains IDEs or VS Code into the environment.
I tried the VS Code server, but had some problems getting it going on ARM Linux (my machine is an M1 Mac, and I'm running nixOS inside an arm64 VM).
I have been surviving on NeoVIM, but really would like to use the editors/IDEs I am used to.
Something like starting up the JetBrains Fleet/Gateway kind of thing, or VS Code server, when entering an env, so that I can connect the IDE to the environment, and it has access to everything in that environment.
Since I don't see me convincing the 20+ people in my team to switch to VIM :)
This shouldn't be more than a day of work.
I've used it as my daily driver for the past year so maybe I can help elaborate too.
image: nixos/nix:latest
before_script:
- nix-env -f shell.nix -i -A buildInputs
This then gives me a CI environment that perfectly matches my dev environment at all times. Some similar story for devenv would be great.Of course I'm not sure how that would work - one of the things that interests me with devenv over 'vanilla' nix is your approach to processes which doesn't map so well in the scenario I've described.
It calls `devenv ci` (which checks that the devenv file is good), then runs a 'script' in the devenv file... https://github.com/cachix/devenv/blob/8bea91cd1073b0d51cf96e...
The 'script' in the devenv file: https://github.com/cachix/devenv/blob/8bea91cd1073b0d51cf96e...
Also, it's called devenv.sh, but.. is it written in shell script?
the adjacent area i’m struggling with is the “i want to write a tiny program to verify some conjecture, and i’ll probably throw it away an hour from now”. think codegolf competitions, if you can’t relate.
the environments i create for these follow similar patterns. it’s either python with numpy, pandas and plotly, or it’s Rust with clap and serde, say. i’d love a tool where i can just `cd /tmp/my-hourlong-project` and then `devenv python` to get a python shell that’ll have everything i often use (and probably more).
hearing from people who use these tools, nobody has told me that any of these can do this — except that since they crawl up the fs tree looking for their env definition maybe i could just stash some definitions at the fs root and get globally-invokable environments that way. seems hacky though: i’d hope for a method that’s more officially supported.
There's a dozen projects that do this. But it would honestly take you as much time to write a shell script to do it as use some other project to do it.
Like, seriously:
#!/usr/bin/env sh
set -eu
[ "${DEBUG:-0}" = "1" ] && set -x
MY_TEMPLATE="${MY_TEMPLATE:-$HOME/.my-template.d}"
_cleanup () { cd ; [ -n "${tmpdir:-}" ] && rm -rf "$tmpdir" ; }
trap _cleanup EXIT
tmpdir="$(mktemp -d)"
cd "$tmpdir"
cp -a "$MY_TEMPLATE"/* "$MY_TEMPLATE"/.??* . || true
[ -e ".init.sh" ] && . ./.init.sh
python* I think I remember that `set -e` means to exit early on error, but I don't know enough of the sharp edges to ever feel comfortable using it given how the few times I've tried I inevitably end up having it set for my entire shell session instead of only within the script and not noticed until my shell exited some time later when I made a typo * I recognize `${DEBUG:-0}` as the syntax to use the value of `$DEBUG` if it's set and `0` otherwise, but I can never remember it without googling * I'm guessing `trap _cleanup EXIT` means that `_cleanup` should be run when the script exits, but it's not clear to me whether `EXIT` is some special keyword, and if so what other values would be valid there * I don't think I've ever seen anything close to the left hand side of the `&&` in the cleanup function. Presumably it means to make sure the directory exists before trying to remove it, but I have absolutely no idea why `tmpdir` could possibly be unset or why it would even be a problem if it was even independent of the fact that I would have thought that the `-f` flag to `rm` would make it ignore the directory not existing * is the final `;` in cleanup necessary? If I got a syntax error without it, I don't think it would occur to me that it would be needed * I've never seen `??` in a path before. I'm guessing it means to try to expand `.*` but then not fail if there's nothing? I don't think I would have even thought of the edge case of the expansion not finding anything being a failure
Could I write a script that does maybe 80% of this fairly quickly? Yes. Would the remaining 20% mostly be not thinking of edge cases and then getting frustrated when using it? Probably. Would I give up at this point and just try to find some other tool to handle it for me rather than doing it myself? Almost definitely.
don't mistake me for a lazy user in the sense of "doesn't want to write my own code". i'm actually the type of lazy user who prefers to find a project that gets me 80% of the way there, patch it where it falls short, and then upstream my changes so that i don't have to maintain an ever-growing pile of custom patches and one-off shell scripts on my lonesome ;-)
There are kind of three levels of language support in devenv:
1) Basic tooling + system dependencies, this allows you to use native language package manager and provide system deps to make sure they build.
2) Compiler/interpreter versioning: pick any version of the tooling as you need
3) Full automatic translation of the language tooling into a Nix expressions for the project. This allows you to get all the benefits of what Nix has to offer. This is the most work for each language but the benefits are enormous.
devenv.sh plans to achieve that step by step :)
Or you could write an alias to do:
mkdir tmp/project && cd tmp/project
cp ~/stuff/scratchbox.nix .
<env-manager-tool> scratchbox.nix
You could even use sym/hard links if you wanted to keep the env file up to date.mkdir /tmp/my-hourlong-project cp ~/my-env-definitions/python.nix /tmp/my-hourlong-project
Is that something like what you mean?
I'm really excited for nix as true sandboxing would be great. Nix however I couldn't get working properly.. and I've been using Linux for almost 17 years.
Once the UX gets sorted, this is a game changer.
Someone has told me that this can be solved with asdf-direnv so I am wondering if this is true and I should actually consider using asdf.
Sometimes I've own image with basic dependencies that I like.
Honest question - do these Nix based tools offer more than that?
Overhead: Windows and macOS can't run Linux-based containers natively. Instead, there's always a full Linux virtual machine running in the background acting as an intermediary and host for your containers. Nix can conjure arbitrary native development environments on a per-command or per-terminal basis, giving you all the performance of directly running tools without the risk of clashing with systemwide software.
Reproducibility: Nix provides much stronger guarantees about the exact versions of software you're running. It effectively gives you a lockfile for your entire dependency chain, all the way down to libc. Containers tend to be more stateful: everyone on your team may be using the same Dockerfile, but if you build an image from it two weeks apart, you're probably going to get very different outputs due to things like your apt-get update step returning new versions of packages. This doesn't happen with Nix.
The beauty is that this isn't either/or; you can actually use Nix to generate OCI container images which are thus fully specified and repeatable.
i.e. if a package depends on the systemd package https://search.nixos.org/packages?channel=unstable&show=syst... , Nix will not automatically find a replacement to run the package on Mac. But it may be possible to manually work around this with https://github.com/LnL7/nix-darwin
More on building Docker images with Nix: https://nix.dev/tutorials/building-and-running-docker-images
Also reproducibility; it can be achieved with containers if you save the artifact (the container image), but that's not what people do in practice, they save only the recipe (the Dockerfile), and if you execute it tomorrow, it will produce a different result than today, and it will likely not even run one year from now (due to e.g. third-party apt repos changing their url, signing keys expiring, curl|bash installers that are no longer hosted, etc.). With Nix, every run will produce the same results, and sources for everything packaged in Nixpkgs are saved by nixos.org.
Can't you just mount a volume from your host machine? Then you can use your regular editor, and just run commands from inside the container.
Any way to hack this? Just pretend it's a different date, the pointers to the latest package will changed to reflect that.
Or somehow record the exact versions when you first build the container, and freeze that.
Also you can have multiple versions of the package cached.
Also all your environments benefit from the cache, since each "layer" is independent.
Docker's layer based caching is very limiting for larger images. With Nix you spend basically no time on incremental builds outside of the time for the one package you changed.
Build once on one machine and all developers can just download the build.
Nix (not to be confused with NixOS) is a package manager. Think of it like apt.
Containers on the other hand are (usually) utilizing kernel level isolation to run a whole user space starting with PID 1. These isolation techniques have overhead.
Since Nix is a user space application, you can run it in a container and Nix provides one `nixos/nix`.
For just the problem of "I want to be able to distribute the same application everywhere", container images solve this well. This can also be solved using Nix instead of container images; or Nix can even be used to build the container images.
The Nix expression language is much more expressive/powerful/elegant than Dockerfile's syntax is. -- Which can be useful for stuff like declaring "I want <program> built with different build flags / patches".
Nix ends up being useful for scratching the itch of "put in effort now, to save effort later".
- Build caching in containers treats the build process as a list of steps: first you do step 1, then step 2, etc. (these correspond to the *layers) of a container). If step 1 changes, you have to re-do step 2, even if it's unrelated; Containers don't have enough context about the commands they are running to know if step 2 depends on step 1.
- Build caching in nix is more like a directed acyclic graph: nix understands the steps that depend on other steps, and when something is changed, nix only has to re-run the steps that depended on that step, and the things that depend on them, etc.
In what way are they slow?
- Nix is very good at cross platform support. A single entry-point creates environments on both MacOS and Linux.
- Docker containers run slower on MacOS because the virtualization overhead.
- Docker images can provide a reproducible environment but the images themselves aren’t reproducible.
The advantage of Nix is similar thought: The first green build of any revision is cached and kept as "that revisions build" forever. Similar like a container.
Installing and using Nix packages doesn't generally involve any sandboxing or containerization features. But on Linux, there are some exceptions. A few proprietary packages use something called an FHSUserEnv, which leverages user namespaces to simulate an FHS-compatible environment. Additionally, Nix (through one of the new, experimental commands as well as an older third-party tool that inspired it) can also bundle any Nix package into a containerized package which can be run without Nix. I think those bundles, if you choose to create them, also use some container-y Linux features.
Anyway devenv.sh isn't built on anything container-y in Nix.
Case in point for my current employer's Python shop - everyone runs PyCharm. Well, JetBrains doesn't really support Nix-based environments. See e.g. https://youtrack.jetbrains.com/issue/PY-42461 . So basically something like this would be DOA. Is this something that someone like @domenkozar can fix? @grhmc ? I don't know.
Setup was easier than Arch and I had more trust in the stability of the system because there's no state/config that I wasn't aware of (albeit after a learning curve).
The biggest issue though is how rigid it is. The "nix way" spreads like a plague into everything and I often felt like I couldn't do basic tasks such as install some node modules without having to relearn how to do it in nix.
I told myself that FHS was a good enough fallback, but that just wasn't the case. Despite using FHS for node development, I still encountered nix-specific issues that needed a nix-specific fix (i.e. using prisma binaries).
I still believe that the Nix way is the right approach, it's just going to take more time to mature out the Nix ecosystem's integrations with various language and package ecosystems to make it more dependable for developers.
I use Python with a few key native packages that are a pain to build myself (NumPy, GDAL primarily). Should I be considering Nix instead of MacPorts?
I use Homebrew for casks and the few CLI tools I need for all my projects (e.g. the Github CLI tool) and I use Nix for CLI tools I need to work on a specific repository using specific versions (e.g. node).
Massive doubt.jpeg, as all my previous attempts at understanding and using Nix have all been "trust me bro, THIS random github repo source code has the Current Best Practice (already outdated)" and various other random two article blogs on how great nix is.
About reproducibility, unless nix promises to fix all upstreams (apt, pypi ??), I don't see how it can fix reproducibility on the client side only.
Concrete example, dev environment with nix but production is Dockerfile with apt getting packages?
Thoughts?
[1] https://nixos.org/manual/nixpkgs/stable/#ssec-pkgs-dockerToo...
> Thoughts?
Canonical answer is to use Nix to generate the image that goes out to prod as well, based on the same versions used in your devenv.
Some differences are incidental (e.g. developers using different operating systems) and Docker can help reduce those, for sure. By some differences are essential and desirable (e.g. you don't deploy your compiler).
I'm starting to think it's best to first reduce the number of differences that matter. E.g. if you're using an interpreteded language, reduce or isolate native dependencies so that most code can just depend on the right runtime version.
> Tangram takes a lot of inspiration from nix, and could be described as a mix between nix and bazel where you write JS/TS
but I don't know what project among these GP was specifically referring to, perhaps it's not public at the moment
I like to idea of having this in every repository in our codebase. Would make bootstrapping easier for new developers. However, you often want a specific golang or python version. Once you update some tool or language everybody gets the new env. That would be neat. Is that possible somehow?
I guess initially we would roll this out just for dev and keep Dockerfiles how they are but eventually we could then use it as builder in docker.
Bonus question: Can I use devenv/nix together with Bazel? We use that in quite a few newer projects and it also suffers from the local dev env issue.
Versions are locked using devenv.lock and everyone gets the same version.
I'm staying away from these Nix wrappers and learning how to do some basic nix-shell work, myself. Until it's proven the wrappers are adding real value, not just trying to hide scary scary Nix from devs who only feel safe in YAML files.
For the curious, this post[2] describes mixing Nix and Docker. And this[3] is a pretty decent tutorial on using Nix itself for this use case (consistent developer environments). Without going full NixOS, setting up a shell.nix with some OS-level environmental dependencies is a great way to dabble.
It seems there may be a sensible reaction away from "Dockerise all the things" for development at least, e.g. this blog post[4]. There are real downsides to containers for development, even though using things like `rvm`, `nvm` and `venv` were also a pain.
We've run into issues ourselves with developers starting to use the new M1 Macs - we've still not figured out how to make filesystem mounting fast yet. There seem to be other advantages to running more things locally, like simpler configuration of debuggers etc. (EDIT for clarity: the issues were with Docker, not Nix, hence the reference to filesystem mounting. Nix == "running more things locally".)
But taking a broader view, while containerising apps with complicated dependencies is a win, we really don't need to pretend that production and dev are actually identical environments. For example, only one of these environments needs a compiler. Once you acknowledge that the developer environment shouldn't be fully identical to prod, then you can start to think about what things do need to be the same, what can differ, and what your actual requirements are.
[1]: https://www.jetpack.io/devbox/
[2]: https://ghedam.at/15502/speedy-development-environments-with...
[3]: https://nix.dev/tutorials/declarative-and-reproducible-devel...
[4]: https://blog.testdouble.com/posts/2020-02-11-the-slippery-sl...
It'll really be wonderful once Nix gains some popularity and some of these kinks get ironed out.
Oh and I managed to brick nix on my mac, for some reason I can’t even reinstall it. Need to spend more time debugging that.
Didn’t play nice with our Rust dependencies as well I found. At least it seemed unnecessarily complicated to set it up with an already existing Rust project.
Highly recommended.
Also when trying to adopt nix for the enterprise it is still a tough barrier. At least for me struggling how to package something like the synology active backup client nix or how to setup your cups printers in nixos.
Besides that I hope for nix to get secure boot support.
Unfortunately, Nix suffers the fate of Haskell: so powerful that the masses can't and don't use it. By contrast, Homebrew spreads like cancer. "Worse is better".
The current state of the software supply chain makes me very uncomfortable to install any packages.
Putting "developer environment" in the name of this tool perpetuates bad practices.
Any tool that constructs environments for applications should be general enough to handle both production and development.
Does this suggest a bit of unknowns in Nix and Nix OS?
The other way around that we build a dev environment that is different from prod just makes no sense.
I, at the very least, completely disagree with you on this specific point as this tool is using Nix.