Deploying Containers on NixOS: A Guide
bkiran.com
bkiran.com
If you want to take this a step further and migrate or run a Compose project on NixOS, I maintain a tool that makes this pretty easy to do :)
k8s: Installing and using k8s can indeed be a nightmare. In my job, we use Azure, so it's not so bad since launching a cluster is mostly handled by Azure. Setting it up for personal use is less fun. The mountains of YAML you can end up using to deploy even semi-complex services is even less fun. That being said, I've been wanting to use it for a personal project (distributed cluster using cloud VPSs and bare metal at home connected using WireGuard). I just wish it was smaller and faster. Most guides recommend 2Gb of RAM and 2 CPU for the smallest of small deployments.
docker-compose: I actually love docker-compose for my personal stuff. I have an intel NUC hosting homeassistant, pihole, caddy, deluge, jellyfin and a handful of other stuff. Everything lives in a series of folders for each service. Backups (both data and code via git), disaster recovery, and just general reasoning about of it is so easy. The docker-compose files are small and easy to read. I also find docker-compose to be about as immutable as you'd like it - version control your docker-compose directories, pin your image SHAs, and you're in a good place. Or don't, and it will still work pretty well.
NixOS: I've done it. I installed it on my Framework Laptop since it was all the rage at the t ime. I lived with it for about a year, and it was okay - for day-to-day use - AFTER I had spent weeks learning how to use NixOS. I will freely admit it's an awesome technology in some respects. But the documentation just was not there. It was way too hard to learn how to do even basic tasks. I thought nix flakes might be the "aha" moment I was looking for, but I gave up trying to get that to work after a couple of days of troubleshooting. Don't even get me started on trying to package up something from scratch. As a random example, I googled "packaging python for nix" and the top result [1] is just way too complex for something that should be pretty simple. The example includes some abomination of a .nix file with inline bash and python scripts.
I don't really know where I'm going with this. I really do like the idea of NixOS. I just wish it was much, much easier to reason about. Curious to hear what others make of this.
Aware that this is more of a critique about the documentation situation, as opposed to the python packaging situation. However, there is poetry2nix[1]. Which makes packaging look something like this:
myPythonApp = mkPoetryApplication { projectDir = ./.; };
[1](https://github.com/nix-community/poetry2nix)(although it has been merged into nixpkgs master)So we're now getting yet another attempt, this time called pyproject-nix[3].
I'm now considering taking similar stab with common lisp packaging, because the amount of time I lost fighting Nix trying to run a development environment is making me reconsider using Nix at all.
[1] https://github.com/nix-community/poetry2nix#:~:text=announce...
[2] https://pyproject-nix.github.io/pyproject.nix/build.html
> I'm now considering taking similar stab with common lisp packaging.
For nix?
Also, thanks for contributing.
I had my own falling out with Nix/NixOS over a year ago. I guess I didn't want it enough shrug.
For clarity, I still use home-manager because it's fairly painless, but anything above that is pretty much a no-go for me.
- Are we packaging just one Python package for Nix, or a thing and all its dependencies?
- Is the package already on PyPI, and are we packaging the source from there, or is it the source from some upstream?
- Are any of the dependencies coming from source, either their upstreams or forks?
- For dependencies that already exist in the nixpkgs SHA that is supplying the Python interpreter, do we want to use those versions, or package newer versions? Is it the same decision for everything, or does it vary?
- Is there already a Python level dependency locking scheme in place such as poetry/pipenv/uv, or is it a plain setuptools package? Is it acceptable for Nix to become the locking mechanism, or will that mess things up for non-Nix consumers of this?
- Are we looking to supply a development environment here too, or is this purely about deployment?
To be clear, none of this is an excuse— it's horrifying that there can't be a single "Tool X is the singular Python-on-Nix story, follow the one page tutorial and you're all set". But I think the massive amount of choice and flexibility is the crux of why new methods and tools are still being rapidly invented and proposed.
For myself, I would choose poetry2nix as how I'd ship a Python project to Nix hosts, but that immediately implies some answers to a bunch of the above questions, mandates poetry for your top level project, and once you look closer there turn out to be some truly horrifying implementation details [1] that are what make poetry2nix appear as seamless and friendly as it does.
[1]: https://github.com/nix-community/poetry2nix/blob/master/over...
I'm not a Python developer, though I use applications that require Python libraries. I don't want a separate numpy for every application I use tucked away in my home folder somewhere; I want a system numpy provided by my distro that can't get out of sync because there's only one copy of it anywhere.
Okay yeah no, even Debian pretty much sucks.
Similar issues (minus insane packaging) is with Common Lisp packages, where there's annoying focus on compiling libraries into binaries (it would have been fine to have a bit of patching to provide things like correct paths for ASDF and CFFI search paths, but no....) which gives absolutely no benefit.
The new rust implementation (tvix) is addressing this as one of its core design goals, allowing evaluation and builds to run in parallel. But even once it's complete and usable, it's unclear what the relationship will be between tvix and Eelco's Nix.
Another is that Nix as language is yet another of those "functional programming sounds fun, let's do bare minimal pure language and forget about all the niceties that proper ones figured out".
The IFD issue is fundamental. It's why poetry2nix has to be implemented entirely in Nix code in order to run at evaluation time without blocking, whereas in a world where eval and build could properly interleave, this would never be done— the core logic of poetry2nix would be implemented in something sane.
In my instance of creating server machines(cattle), the configurations are pretty light and what's important is the reproducibility aspect of it. If I need to take one down and rebuild another it takes about 10 minutes. All the upfront work of configuring Nix for that one machine has paid off.
That said, I know nix pretty well at this point and I would probably have a different opinion if I hadn’t spent so much time learning nix.
Yeah, I'm in exactly the opposite boat—I've been using docker professionally and at home for 5+ years now and know it very well, while nix is still very new to me!
There's probably no way to objectively tell which one we'd have preferred if we started in the opposite order.
Minor nit: configuration is not the hard part. The hard part is getting "/root/registry-password.txt" onto the NixOS machine in the first place. I mean, you could just scp it I guess, but why spend hours tuning a NixOS config that requires you to manually do stuff in the end?
I'm aware of all of the NixOS "secret management" methods out there but I found none of them satisfying back when I was still using NixOS.
It's really only deploying on unmanaged servers where this comes around, but it's also somewhat of a hard problem. Like you don't (or shouldn't) bake secrets into disk/VM/container images, so once you're no longer building on some managed layer then you do have to figure out bootstrapping yourself.
I have some playbooks I setup to creating a new machine. All in all it takes about 10 min to get it up and running. Maybe not instant but at the moment I don't need anything else.
Also, SCPing the file over is bad for reproducibility.
For the topic at hand isn’t this always a problem with deployment systems? You need to have the secret somewhere after all. In my case I only ever use nix for personal systems, so feel totally justified just storing my ssh key as a secret in yadm.
Also please don't tell me k8s it. Way too heavy.
I run a few applications on a souped-up Raspi (paperless-ngx, jellyfin, some postgres dbs). I can't see why this would improve things. :)
Honest question.
But because the individual software on nix is so well separated / encapsulated it carries similar benefits to containers. So they’re different but with overlap
While docker-compose allows you to compose your containers with a yaml/Dockerfiles, NixOS allows you to compose the system that all of your containers run on (from userspace down to kernel selection/configs, file system, etc), as well as your containers - all in a declarative .nix file. That .nix file can be used to spin up any number of identitically configured systems.
It's also reproducible, in that you can specify the sources (refined to a specific commit if you prefer) for any and all packages on the system - and build them with Nix within a sandboxed environment protecting dependencies and env configurations (Nix is also a powerful build system).
You just: - Mange/setup nixos - Add container architecture definition to nixos config
The containers, being systemd units, would have all the normal systemd log management like the rest of your system, instead of having to dig through docker-compose logs with a different mechanism than "normal" systemd service logs.
You'd also get all the normal benefits of a nixos system: the config file can be placed on a new system to completely reproduce the system state (modulo databases et all), rollbacks are trivial, etc.
I really like being able to manage the managing and host machine in one configuration. It's a blessing from an Op's perspective.
1. There are many services that is already "implemented" in NixOS, with sane default configurations and easy to customize (because the contributors have designed good abstractions, and also because of the flexibility of Nix language). One good example is `nginx`. Btw `paperless-ngx` and `jellyfin` are also already implemented. In this case you do not need to use docker at all.
2. Because of the good abstraction in the service implementation, I usually do not need to go very deep to understand the common configurable options for each of the services.
3. All those services become systemd services once up. As long as you are familiar with how to manage systemd services at runtime, you know how to work with them.
4. Even for those ones that do not exist in NixOS, as the authoer suggested you can still start them as docker-based systemd services, with very simple and intuitive nix configurations.
5. NixOS configuration are mostly deterministic and modular. I can use git to manage all the configurations for different servers. There can be occasions that I will need to migrate the services to a differen machine (e.g. upgrade, replicate, ...). With the NixOS configuration of those services, I can simply re-use the configuration code and have a very high confidence that they will work as expected on a new machine.
6. The above also makes it very easy to revert my deployment to any previous successful version. Without having to worry about breaking anything, it also gives me the confidence to quickly try out different ideas.
What entity is responsible for the security of those combinations of default settings? And how are security updates handled?
Currently, run my own k8s cluster with 20 worker nodes (basically just VMs on a few computers). Able to not only containerize my workloads but also evacuate workloads to different workers when I need to take down the server for maintenance (os updates, moving, kubectl upgrades).
I had actually planned to setup another remote cluster in my parents home (800 miles away), but ended up 86’ing that because their residential internet is the absolute worst.
Currently cluster only accessible when behind VPN or on local network. Haven’t setup proper authN/authZ controls yet.
Especially source repo needs to live on some sort of striped zfs array. Ordered one of these quad nvme NAS things [0] so that's probably going to be storage. Either nix or proxmox...not decided.
Also still a bit fuzzy on what best game plan on PVs is. Minio/s3 or nfs appear to be options.
DB...just the usual suspects...mainly postgres for gitea I think. Mongo for dev stuff. Used to vanilla proxmox/docker/lxc so this is all unchartered territory for me.
Okay nice, seems like various storage services for a home lab setup. Seems like a cool project. Especially if you can distribute it across all those NAS's
Multiple worker VMs per physical host? Why?
Depending on your threat model that could make sense.
> Also most workloads wouldn’t need to use all cores or memory on that machine (one machine has 128G)
What? Limiting CPU or memory use of a pod is one of kubernetes' core competencies; just tell it what each thing needs and it handles all that for you.
I would like to do without the headache of dealing with K8's installation or some orchestrator layer. I can "schedule" my on application instances at the size I'm working with.
Also, I run this setup on cloud vm(multiple actually). So it's not restricted to running on a single machine running in a closet.