Hacker Newsnew | past | comments | ask | show | jobs | submitlogin
Building with Nix on Replit (replit.com)
82 points by amasad on Oct 2, 2021 | hide | past | favorite | 48 comments


To see and play with what people are building with Nix on Replit check out the #nix hashtag here: https://replit.com/apps/nix

Users vary from teachers building environments for their classrooms, to hackers tinkering with emulators, to kids making old games like run online.

We think we’re just scratching the surface with what we can do with Nix. It’s an outrageously underrated technology.


> We think we’re just scratching the surface with what we can do with Nix. It’s an outrageously underrated technology.

Super agree and watching from afar. Would love to see a blog post from replit "why not docker" (which is likely to go into how docker is the wrong sort of encapsulation, caching, source/binary substitution?)


Docker and Nix are orthogonal; Docker is mostly a tool for packaging things in a standard way, Nix is a way of specifying build instructions.

But many people (ab)use Docker to solve the kinds of problems that Nix would have been better suited to solve.


Some people use Nix to build Docker images. They say it's very good for that.


It is, nix is more declarative than a dockerfile and can even optimize the layers based on the dependency graph. See https://grahamc.com/blog/nix-and-layered-docker-images for a good explanation and https://nixos.org/manual/nixpkgs/stable/#sec-pkgs-dockerTool... for all of nix’s container related documentation.


See https://nix.dev for the best education resource.

ps. to run nix on gitpod use https://github.com/gitpod-io/template-nix

pps. i'm doing a talk on the 6th on nix. Cya there? https://twitter.com/GeoffreyHuntley/status/14424751054271528...


Added nix.dev to the list of resources in the post. Thanks.


> For reasons of security, this user does not have root privileges, and therefore cannot install packages using a traditional package manager like APT.

Okay, I'm going to nitpick: Termux proves this isn't a requirement of apt, just most distros using it, and in any event fakeroot/proot would solve it if that was the goal. Of course, nix is a mostly good system with loads of other benefits; it's only that specific reason that feels weak.


I want to love Nix but I just can't get it. Worked with it for 14 months and found I struggled to make small changes meaning we had to depend on DevOps for things I could whip up in Docker and bash in minutes. I don't even really know why I struggle with it so much, but the startup I'm working for mentioned using it and I just shuddered thinking about it.


> we had to depend on DevOps for things I could whip up in Docker and bash in minutes

I absolutely believe you (Nix has a bit of a learning curve), but just as a counterpoint I'd like to mention that I've been using Nix with all sorts of languages (compiled and interpreted), for various projects (local applications, cloud services, static HTML sites, etc.), on Ubuntu, NixOS and macOS.

I recently had to interact with Docker for the first time and found it very bizzare (annoyingly, lots of documentation (e.g. that of AWS ECS) assumes its readers are already familiar with Docker!). It took many attempts to install it, using various different guides, versions, etc., many of which turned out to be obsolete (e.g. "docker machine" was recommended, but that's apparently replaced by "docker desktop", but now I'm hearing I should use "docker compose"?).

After I eventually figured out how Docker works, I ditched it completely since I found it easier and more reliable to build container images using Nix and run them directly.


I'll give Nix another go, but I find it hard to just start scrapping things together. For example how would I create a dev environment for a simple Node app, managing Yarn dependencies and connections with a postgresql db? These are the sorts of things I'd love tutorials on doing


I'm not a heavy Node user, but I do have a project which happens to use Yarn. Not sure if it's the best approach, but this is how I'm creating the node_modules folder:

    nixpkgs.yarn2nix-moretea.mkYarnModules {
      name        = "my-project-deps";
      pname       = "my-project-deps";
      version     = "1.0";
      packageJSON = ../package.json;
      yarnLock    = ../yarn.lock;
    }
I copy things around, invoke commands, etc. using `nixpkgs.runCommand`, which runs shell code (a lot like rules in Make).

I'm not sure about postgresql, since I've not used it before:

- You could use Nix to install the packages, and manage it yourself. That wouldn't be particularly different from non-Nix setups.

- You could install it on NixOS by specifying these options in your config https://github.com/NixOS/nixpkgs/blob/release-21.05/nixos/mo... (they're also listed at https://nixos.org/manual/nixos/stable/options but that page is slow). Of course, that requires NixOS (or a VM).

- You could run it in a container/VM, e.g. like https://nix.dev/tutorials/integration-testing-using-virtual-... but that seems a bit more advanced.


I appreciate the time you put into this response, thanks! I’ll give it a go


Unfortunately there’s no shortcut other than investing several hours reading tutorials and then repeatedly breaking stuff on your own machine until you start to understand.

It’s worth it though. There’s something magical about having an entire machine configured declaratively.


I think it's comparable to using Terraform.

If you're only setting up one server in some cloud, then probably it's easier to just set that up through the admin dashboard compared to setting up Terraform. For such a limited usecase, the benefit of "infrastructure as code" is that at least what you've got is documented.

But with using Terraform, all sorts of use cases are then enabled beyond what you'd want to manage manually.

Using Nix instead of manually installing a bunch of stuff seems fine either way. But stuff like nix-shell paired with direnv seems pretty neat.


I actually really agree that Terraform is a good analogy - you're frontloading the effort in a way that hopefully will make things easier in the future.

I use Terraform to manage my own personal AWS account. Recently I needed a Windows VM to run some random exe file. I could have created an EC2 instance adhoc through the web console but I did it through terraform instead. It probably took me about 30 minutes longer than it would have otherwise, but now I've got that terraform module ready to go if I ever need a windows instance again in the future. It's got a properly configured security group that only allows access from my VPN, and it outputs the IP and admin password automatically. I'll never have to solve that problem again (well, for a few years if we're being realistic).


Sadly in a truly Nix'd up DevOps environment it takes so much more time than 'several hours'. I got to grips with a local NixOS computer - that was fine - but when you're working with 10kloc Nix environments it's just so so hard to grasp.


I tried to like nix. I spent many hours tweaking my macOS setup but now I’m back to brew. The nix language is just too foreign to my way of thinking. I love the concept behind it but the language is a huge barrier to entry to many people and I believe it will not gain nearly as much traction as it deserves until this is addressed.


Yeah, it’s not as nice as macOS. There’s a ton of magic happening too, so I found it easy to get lost and not fully understand what was happening.

I had tried to compile some software that linked against macOS SDKs, and did not realise that Nix essentially maintains it’s own version, and it was quite tricky to adapt to it or get back to using Xcode’s toolchain.


Could you say more what's foreign?


Its probably that I grew up with c like languages. When I try to read something like

{ pkgs }: { deps = [ pkgs.cowsay ]; }

It’s meaningless to my brain.


To put it in a C context: it’s a function with a single argument ‘pkgs’, which returns a struct with one field ‘deps’, which is an array of package structs.


So why did they chose such unconventional syntax? Just for the hell of it? There doesn't seem to be any advantage and it definitely puts a lot of people off.


The syntax is not so unconventional compared to, say, modern javascript:

({ pkgs }) => { deps: [ pkgs.cowsay ] }

or python:

lambda pkgs: { deps: [pkgs.cowsay] }

I guess really the question is why create a new language rather than using an existing one? The language needed to be functional, lazy, and good at managing packages, and not much else. I guess they felt that no existing language fits the bill (Haskell does I guess but it's got a lot of baggage). I can't claim to know much about its history.


Translating to modern JavaScript in three easy steps:

  { pkgs }: { deps = [ pkgs.cowsay ]; }
  ({ pkgs }): { deps = [ pkgs.cowsay ]; }
  ({ pkgs }) => { deps = [ pkgs.cowsay ]; }
  ({ pkgs }) => ({ deps = [ pkgs.cowsay ]; })


Your translation is incomplete. You're ending with:

> ({ pkgs }) => ({ deps = [ pkgs.cowsay ]; })

But the syntax for declaring objects in javascript differs. It would be:

    ({ pkgs }) => ({ deps: pkgs.cowsay })
The fact that '{ x = 1; }' is declaring an object, identical to '{x: 1}' in javascript, is important.

It's also worth pointing out that, while '{ pkgs }:' is idiomatic nix, idiomatically in javascript you'd probably just take multiple arguments (so '(pkgs) =>', not '({ pkgs }) =>' in js). The reason nix uses an object there is because a nix function only takes one argument, so there's a fairly common convention to make that argument an attr in order to simulate multiple arguments. That convention doesn't exist in quite the same way in js.


You forgot the array ({ pkgs }) => ({ deps: [pkgs.cowsay] })

I also translate all nix to JavaScript in my brain


Helpful. There once was a command line tool that would turn a C declaration into english (like cdecl.org) I’d love to see something like that for the nix language (and kotlin too since I’m dreaming)


We’re trying to make it more accessible. Making a GUI or a no code interface is also on the table. But nonetheless we are seeing kids build nix environments. See this hashtag: https://replit.com/apps/nix


I'm currently using NixOS. I jumped in some time after using Nix with macOS.

I think following simple examples is straightforward. I think anyone who can install python and ruby with brew would be able to look at a replit.nix which uses python, and figure out how to change it to also install ruby.

Even for just simple cases, I think Nix is compelling and worth looking into.

"Nix is weird" becomes more of an issue for a long tail of edge cases, where a developer is likely to bump into one of these, and it won't be clear where in the documentation to go. -- With Nix being so weird, it both requires a familiarity with what you're doing in a standard-linux way, as well as familiarity with how things need to be done with Nix.

e.g. the nginxModified in the OP makes use of overrideAttrs. (You'd need to know both that nginx can be configured like that, as well as how to configure with nix).


Are there any good initiatives you think companies should fund to improve the accessibility?


I think donating to the Nix foundation would be as good as any.


Kudos to the author(s), this article really is a nice written technical introduction, it has all the bases covered for such writing:

* it explains in plain English what Nix is.

* it provides additional resources

* it has hands-on, guided, deliverable, tutorial.


This is a better introduction to Nix than the official one. One minor nit:

> Functional languages are composed of functions that take some input and produce some output. Every time a function is executed with a given input, it will return the same output.

Surely that is a property of pure languages, not functional ones. Maybe there are no pure imperative languages but I don't see why you couldn't have one. In fact it would probably make way more sense for Nix given how much easier imperative languages are to understand.


Pure+imperative sounds useless. The imperative steps wouldn’t buy you anything. If you already can do let-bindings then you get nothing extra from imperative statements.

Something more useful is an imperative language which only uses immutable values (strings, lists and so on).


It sounds well but working crappy. Tried to install several nix packages using their tutorials - all failed.


> Functional languages are composed of functions that take some input and produce some output. Every time a function is executed with a given input, it will return the same output.

I think this is not true. If it was this way, there would be no way to read data from an API. read('https://news.ycombinator.com') will not return the same output everytime.

And it would not be possible to keep track of anything. No counting, no transformation of data, nothing.

Googling a bit indicates that people use a mental trick and define the state part of the input of the function. So in terms of getUrl() the current state of the internet is part of the input of the function. And when counting something, the function gets the old sum as the input and outputs the new sum which gets stored in a different place than the old sum so the old sum "did not change".

I see a lot of overhead coming from these mental gymnastics with no benefit. Any short example that shows a benefit?


A function which returns the same output (and has no side effects) if given the same input is a "pure function".

Things like network requests, or reading from files, or keeping track of state like a counter, are effectful. So, yes, a function which does these things couldn't be pure.

It's still nice to be able to have a pure function over an impure function. (Sortof the same "nice" as in "it's nice to not use global variables for everything in your code"). An impure function might be more difficult to think about than a pure one.

In languages like Haskell, all the functions are pure. But useful programs are effectful. There are different ways Haskell can still have useful programs while only allowing pure functions. (The most common way this gets done is using monads). -- One trick is "algebraic effects", this blogpost is an accessible intro: https://overreacted.io/algebraic-effects-for-the-rest-of-us/

Functional languages themselves are about a bit more than pure functions. It's about being able to pass functions around.. languages like JavaScript or Python have functions like map/filter/flatMap. Functions which take functions as parameters. They're a neat way of writing code.

The JSON program JQ's language is comparable to Nix. -- You could write your JSON-processing programs with Python (or whatever), or you can use some JQ expressions to do the processing.


The needs of the Nix language differs from that of a general purpose programming language. Its sole purpose is to output a self-contained and deterministic package build script. As such, it doesn't need persistent state. Every external input, such as source tarballs has their hashes verified so it should wield the exact same result every time it's evaluated. If any external input doesn't match expectations, evaluation would be aborted immediately.

> And it would not be possible to keep track of anything. No counting, no transformation of data, nothing.

All of this is possible without state. Counting could be done with, say, 'builtins.length'. As for transforming data, that's what programming languages literally do. The Nix language isn't an exception here. In fact, the Nix language has everything to do with transforming data. It transforms high level package definitions to lower level build instructions.

> I see a lot of overhead coming from these mental gymnastics with no benefit

At least with the Nix language, there's no mental gymnastics going on. Defining a package is as simple as it gets, as can be seen here[1]. The benefits are reproducible package builds and composability. Since Nix functions don't contain state, it's really easy to combine abstractions and make modifications to existing packages compared to conventional package managers. See the Chrome example in the NixOS Wiki[2].

[1]: https://github.com/NixOS/nixpkgs/blob/master/pkgs/applicatio...

[2]: https://nixos.wiki/wiki/Overlays


With Nix, any network I/O is done through "fixed-output derivations." Fixed-output derivations define the expected hash of their outputs, so if they don't produce the expected output they throw an error.


This seems like another mental trick to me.

"Yeah, the function did not produce a different output. It just behaved different by throwing an exception."

Still, some other code will read (or "interpret") this behavior and then behave accordingly etc. So what is won here?


Nix doesn't allow for recovering from errors


It's a good trick.

The function didn't return a value, so its caller cannot interpret that value and cannot change it behaviour.


Your absolutely right: it's not true. Well, not entirely true.

There are two ways functional languages get around this:

Most functional language don't force functions to be pure. So, while they tend to be composed of functions that produce the same output for a given input, they aren't entirely. That is, they're just like any other language in this respect. Nonetheless, functional programs tend to separate the impure parts using patterns such as https://www.destroyallsoftware.com/screencasts/catalog/funct...

Then there are the languages where functions actually are necessarily pure - mostly Haskell. But this just means that the imperative actions aren't functions, not that they don't exist. The big impact is that, since a pure function can't invoke an impure action - or it wouldn't be pure - you have to use function-code-imperative-shell or your program won't compile.


> I think this is not true. If it was this way, there would be no way to read data from an API.

Why do you think the only way to read from an API is with a function call? Have you not heard of monads?


Nope. I have not heard of any of this. I only read this sentence in the article:

> Functional languages are composed of functions that take some input and produce some output. Every time a function is executed with a given input, it will return the same output.

It does not mention "monads".


in pure FP languages there are functions and IO actions, functions are pure, IO actions are side-effectful.


Was guix considered at all? If so, what were the negatives?

I'm personally getting more enjoyment learning this style of environment and OS management with a more established language for the configuration.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: