Advantages of Nix over stack - Nix

Welcome to the Functional Programming Zulip Chat Archive. You can join the chat here.

codygman

I've been moving between pure stack, stack --nix, stack inside of a nix shell, and cabal in a nix shell.

I'm curious what others see the advantages of any of the nix variants above are over just using stack.

codygman

One recent real world example:

Different versions of tinfo6 we're used with ghc based on stack.yaml settings changing that caused spurious rebuilds in our dev docker containers

Torsten Schmits

when I'm developing, I only use ghcid. then to ensure the health of the whole project, I do a nix-build

Torsten Schmits

what tasks do you perform and how do they differ for your several variants?

codygman

At work we have a more complicated piece that rebuild binaries, copies into an exposed volume, and restarts docker containers

I'm thinking about moving that into nix build but I'm not sure how tests with external services are usually handled in nix expressions

Joel McCracken

I think it does in some way (that isnt actually useful) but i interpret cody's comments somewhat differently

Joel McCracken

with stack the way to deal with docker is somewhat straightforward; make an image with stack and buidl dependencies all installed, build the project, make another image with runtime deps installed, copy the binary from original to runtime, deploy runtime image

Joel McCracken

but that all feels so much more confusing/unclear in a nix world; do i really need to build an image with nix and ALL of that installed? haskell images are already giant (like 10 gb is not uncommon), is it sensible to add nix on top of that? then what do i copy over to the runtime image? ok the executable, but what about the runtime deps? do i have to copy all of them? I guess they have to match the directory structure of the build environment too?

Torsten Schmits

with nix there are also pretty useful tools available:

pkgs.dockerTools.buildImage { name = "foo"; exe = myoFooExeDerivation; };
Joel McCracken

and then do i have to actually install the nix tooling to get things to work right when I run my application?

Joel McCracken

(fwiw I have done zero research on it, this is just where my brain went when reading cody's comment)

Torsten Schmits

it is definitely helpful to use justStaticExecutables to cut down on size, and then you'll run into problems with native libs.
I've got a very well working setup with static-haskell-nix though, reducing image size from about 1G to 20M

Torsten Schmits

Joel McCracken said:

and then do i have to actually install the nix tooling to get things to work right when I run my application?

well you always need to install something if you run your app with your build tool

Joel McCracken

right well what i mean is do i need to run it inside nix-shell because of various environment variable setup stuff

Torsten Schmits

I'd say that exactly this is the intended purpose of nix :smile:

Joel McCracken

so the answer is that i can't just copy the binary over to the bare image, i need to set up nix tooling on the runtime image

Torsten Schmits

not quite sure I understand the scenario. in my example, nix builds the image that contains nothing except for the static app binary

Joel McCracken

so how do you run that binary?

Torsten Schmits

aren't we talking about docker?

Torsten Schmits

there's just a native binary, that you put into ENTRYPOINT

Joel McCracken

haha sorry. so there is a command in a docker image; its either specified in the dockerfile (using CMD IIRC) or you can change it with the run command

Torsten Schmits

no deps, glibc and everything linked in

Joel McCracken

ok so you don't HAVE to do nix-shell --foo '/path/to/asdoifjaisdof9899/static-binary'

Joel McCracken

you'd just go to /prod/static-binary

Torsten Schmits

no, in my case I just use a naked alpine with a blackbox ELF executable

Joel McCracken

that's great to know, thanks

Torsten Schmits

let me know if you want more information!

codygman

Torsten Schmits said:

stack handles docker?

At work we use stack inside of a docker container. Then we have a script we run that loops rebuilding our main app and a few external services with stack build --fast , copies them to the correct location, and restarts the docker containers.

Rizary

You can do interesting part with Nix, e.g https://twitter.com/zimbatm/status/1309059217849024513

I haven't found better solution for development in Haskell (I'm using VSCode) besides ghcid. Maybe I'll try HLS in the next future. I'm not sure if I want to use stack or cabal in my development flow with haskell.nix

codygman

So imagine this scenario and compare stack vs nix:

Application foo

Service bar

DB baz

With stack you can use stack in a docker container and docker containers for the other service and DB too. This works alright, but breakages tend to happen when upgrading, or at least way more rebuilding than you might like.

With nix you can do:

  1. avoid docker containers entirely and use stack. Use nix to bootstrap packages in GHC_PATH and make sure stack follows this
  2. build each of the docker containers with nix (maybe even using Arion?)

Option 2 seems nice, but it's much slower than the docker equivalent because nix-build is slower than stack build --fast. I'm betting that (but hope I'm wrong about) stack build --copy-bins && PUT /request/to/restart-docker is faster than nix-build with option 2 and restarting the freshly built docker containers.

I think most people use option 1, but use Cabal instead of stack. Cabal didn't seem to ever ignore GHC_PATH, but with option 1 if you call a stack command before an environment is rebuilt with lorri, stack will take it upon itself to install the packages in your .stack-work directory.

Hopefully all of this makes sense.

codygman

ALL of that said, this is kind of besides the point I wanted this thread to serve :laughing:

Not that I mind, since other questions I have are also being answered :smile:

I actually created this because @Sridhar Ratnakumar said in the thread about "what would you change about Haskell" that stack should replace nix. This made me wonder what real world advantages people have had using nix over stack. The kind of advantages that you could use to convince a die-hard stack lover that nix is worth it for instance :wink:

codygman

Rizary said:

You can do interesting part with Nix, e.g https://twitter.com/zimbatm/status/1309059217849024513

I haven't found better solution for development in Haskell (I'm using VSCode) besides ghcid. Maybe I'll try HLS in the next future. I'm not sure if I want to use stack or cabal in my development flow with haskell.nix

So this is one big advantage of nix over stack I keep in mind as well:

  • developers locally can build something during the course of regular development and that build gets pushed to cache
  • CI runs nix-build-uncached and no build needs done since we can go by the guarantee of the developer having built it locally
  • CI times shrink from 5m-7m down to 1m-2m
Rizary

Nice to know that. I never use stack, so this is interesting. For me, I usually use plain haskellPackages, then doing nix-shell and use cabal instead of stack.

Recently, I use more of nix flakes, and the development speed is really fast (I'm using rust and go in my day to day work). I want to start some haskell project next month, so I wonder how I can have good setup for haskell in nix.

what IDE do you use? I'm using VSCode, so I wonder this https://github.com/xtruder/debian-nix-devcontainer? will saved my time. The idea is hooking up that, with stack available in my container, and spin up the project with VSCode and docker inside of it. It is still raw idea, but from your saying, I think I want to try stack at least for once.

VSCode devcontainer that uses debian as base system and nix for management of development environment - xtruder/debian-nix-devcontainer
Torsten Schmits

@codygman could you clarify why you're running the app you're hacking on in a docker?

codygman

Torsten Schmits said:

codygman could you clarify why you're running the app you're hacking on in a docker?

I don't think it strictly has to be and that's just how things are currently.

I've considered just running the haskell application and haskell service locally. However there is also in the real world example (not the one I gave) another quite large javascript application with many functions including populating one of our databases.

Further I've considered replacing our bash script that restarts all of those services to have be a haskell function and then using ghcid to do this like ghcid --test=restartApplicationAndService or something.

I'm not sure the best way forward though.

Torsten Schmits

so your app and service are part of the same compilation unit and you restart them both after building?

codygman

Torsten Schmits said:

so your app and service are part of the same compilation unit and you restart them both after building?

Yes, exactly.

Torsten Schmits

sounds rather cumbersome!

Torsten Schmits

have you thought about running two ghcids at the same time?

codygman

Hm, I haven't looked at that... no. I think the services change infrequently enough for it not to be a concern though.

Torsten Schmits

oh, multiple services? like, you're running the entire company infrastructure on your dev machine? :upside_down:

codygman

Torsten Schmits said:

oh, multiple services? like, you're running the entire company infrastructure on your dev machine? :upside_down:

Yep, that's correct entire infra over about 4-5 docker containers :big_smile:

Torsten Schmits

:laughing: sounds serious

codygman

I'm not sure what the alternative would be to have a reasonable change compile cycle. Not sure if you are implying there might be a better alternative.

Torsten Schmits

well obviously I don't know your requirements but for building features I would strongly prefer smaller environments with in-memory variants of service dependencies

Sridhar Ratnakumar

neuron uses nix+cabal, with ghcide as well as HLS (run nix-shell --run 'code .' for a ready dev environment). there is docker.nix that builds docker image by copying over nix-build's binary (weights ~150mb). no need for stack or docker, really. use nixos for multi service deploy.