TL;DR Does anyone have an example of Typescript monorepo that uses Nix and deploys multiple packages to services in a single Nixos machine.
Cross-post from reddit so it gets some more visibility
I’ve been using Nix for managing my shell environments and dotfiles (via home-manager) for a little while now but struggling to apply it further in my projects. Like most I’ve found learning nix a challenge due to scattered docs across manuals and blog posts and constantly shifting ways of doing things. I pick up small chunks of code but am struggling to apply multiple aspects to a real world project. I recently read the Nixos in production book which I think is a great start on trying to provide guidance (I’m aware the book is still in development) in an area I think Nix can really shine.
I’ve got the opportunity to overhaul a project that consists of a number of typescript packages and would like to ‘nix’ it. It’s a Typescript monorepo that has a backend and frontend with shared packages. There is an example repo here: GitHub - nathanscully/pnpm-monorepo-test I’ve setup to mirror what I am working with. The current state is that I can generate docker images for each app via a single Dockerfile. It is using a rudimenty Nix shell but otherwise everything else is left to PNPM and Docker.
I’d like flexibility and simplicty in deployment by moving to a single Nixos machine that runs the packages as services with nginx handling subdomain routing. I.e. app.domain.com goes to the frontend and api.domain.com goes to the backend. Developers should be able to run the machine locally and any changes to the repo should allow the image to be rebuilt and redeployed in prod.
Goals:
- Be able to run
nix run
on a developers machine and launch a nixos VM that has the packages running as services and fronted by nginx. I.e. replacedocker-compose up
- Be able to run
nix run .#local
or similar and have the services execute on the host developers machine. i.e. wrap pnpm start. Run postgres as a local service etc, essentially replaceing manually manging runing services. - Be able to run
nix build
and default to building the machine locally - Be able to run
nix build .#docker-frontend
and build a layered docker image of only the frontend package - Be integrated into a CI/CD pipeline so that any changes to packages are redeployed. The Nixos in production book has a good example of this using system.autoUpgrade alongside an intial terraform deployment
- Bonus: I’d like flexibility in dependencies based on environment, specifically for databases. For instance when run locally the machine can have a postgres service running along side the packages. However in production we would want to point to an external DB, i.e AWS RDS.
Why:
- I want to be able to just deploy a machine and have it work. I don’t want to stuff around with multiple docker containers, or load up a single docker container with multiple processess.
- I want a developer to be able to run a single command and get a working environment like production, including service dependencies as efficently as docker-compose is.
- I’ve been so impressed with using Nix on my personal machine I’d like to champion it as a deployment option for services.
Challenges:
- Developers use aarch64-darwin machines. I’ve been able to setup a linux-builder using nix-darwin.
- CI/CD is currently via github actions which I believe doesn’t support aarch64 still. I’d be happy moving everything to aarch64 and deploying to graviton ec2 instances and have the CI/CD builder hosted somewhere else if it makes life eaiser.
What I’ve tried:
- mkShell - works great to help manage #2 but doesn’t help with the build/deploy side
- devenv.sh - Useful as a mkshell wrapper. I’ve used it in a few projects to get shell environments running. Can help with #2 as it supports easily spinning up services. However I’ve run into cross architecture issues and feel like it will be limiting to setup machines with multiple services.
- Hacking on flakes. I may just not grok Nix enough yet but I keep going down deadends trying to make what I think should be basic things work. For instance: should I use dream2nix (seems like its going through massive changes atm), buildNpmPackage (struggling to find good examples of it working across workspaces) etc.
The ask:
If anyone has a good working example or experience getting and end to end setup like this working and can share it, please do. I’ve seen some examples that are 80% of the way there but might be in golang or Rust for instance and I get stuck trying to build the NPM packages as a services. If am also overestimating Nix’s maturity to be able to simplify DevOps in this current point I’m happy letting it sit for a while and revisiting when there are more updates i.e. dream2nix has gone through its transition.
Alternatively if you feel this is a missing example that would benefit everyone I’d be happy working with you trying to flesh this out as an example to contribute back to the community. My personal goal is to keep tackling it bit by bit when I get the time and documenting my results.
Given the large scope if people can share even working samples of chunks of the above I can keep working on compiling it into a single working example repository. For instance packing the ‘server’ package in the example repo into a service that runs on a nixos machine. I’ve not found any great examples of turning a nodejs package into a systemd service.