Personally I've been making OCI images using Nix, just by running tar, sha256sum and jq:
- We can get a Nix derivation's dependencies using `writeDependenciesToFile`
- We can put these dependencies (alongside anything else we like) into an OCI "layer" using GNU tar: the `--hard-dereference` and `--mode=755` options work well, and we can include directories using `-C /foo --filesFrom=foo.txt` where foo.txt comes from `cd /foo && find . -maxdepth 1`, with the leading `./` removed.
- A "digest" is just a JSON file like `{size: 123, digest: "abc"}`, e.g. generated using `stat` and `sha256sum`
- Those sha256sums can be referenced in a config.json, like `{"rootfs": {"type": "layers", "diff_ids": [...]}}`; along with the application-specific config (EntryPoint, WorkingDir, etc.)
- Finally, a "manifest" is just another JSON file; with `{"mediaType": "application/vnd.oci.image.config.v1+json"}` for the config digest, and `{"mediaType": "application/vnd.oci.image.layer.v1.tar+gzip"}` for each layer digest.
Unlike Docker, these commands can all be run directly (from the "host", although it's not really hosting anything, since we don't need any containers or VMs to run the above); their contents are completely declarative (since we're just zipping up pre-existing files, e.g. built/fetched by Nix; rather than mutating a filesystem in-place); we can do it on any OS (e.g. no need for a Linux VM on macOS); etc.
There's a lot of cool things in here, some we're excited to explore and some that I'm first hearing about right now
We could jam back and forth on this but generally we're not dogmatic about the "middle" of the process, and we suspect the implementation will change over time for a variety of reasons (even faster builds, smaller images, less dependencies, etc).
The only thing we're dogmatic about is the experience and the fact that it emits an OCI compliant image (that can indeed be run on any orchestrator).
If you're interested in contributing or jamming on this wanna shoot me a DM on twitter (JustJake) or email at jake at railway dot app?
This is a low value comment and I deserve any downvotes I get: but I Fucking Love (TM) when people just slam out working shit like this without any red tape. Well played.
Having equivalents like ociTools.buildImage and ociTools.streamLayeredImage functions would certainly be nice; but at the moment there's only ociTools.buildContainer, which doesn't use images.
I'll see if Nixpkgs is receptive to having such functionality, as a unification of what I've done with what exists in dockerTools (although I need to check that my employer's happy for it to be upstreamed first!)
I've been using nix2container[1] for awhile now. It looks like this depends on Docker, have you guys considered removing that dependency? It shouldn't be necessary to create an OCI compliant image with Nix.
Do builds use sandboxing? It appears not[0], is this in the roadmap? Based on my experiences with node2nix, composer2nix, carnix, etc, I don't blame you for not going that route. Not to knock on the developers of those tools - it's a difficult problem. I apologize if my interpretation is incorrect.
Somewhat tangential, but I'm curious how big the bootstrap seed for Nix is. That is, if you wanted to build the entire world, what's a minimum set of binaries you'd need?
Guix has put quite a bit of work into this, AFAIU, and it's getting close to being bootstrappable all the way from stage0 [0]. Curious if some group is also working on similar things for Nix.
I would think the bootstrap for cross-compile would be pretty close to the answer to your question.
Guix has pretty radical goals in comparison to Nix, which is itself relatively radical. Bootstrapping Nix is... maybe less useful to optimize? I'm not saying it's not worth solving, but as an end user I really don't care if my packages are built from source or not, as long as I can, for example, use Spotify.
This is really cool, and reminds me of some parts of Domen Kozar's recent talk titled 'Nix is going mainstream'¹. The crucial work of putting together this kind of really simple frontend seems like what he's talking about when he says we need 'our Docker', like how Docker built upon old tech (chroots, jails, LXC) to make it radically more usable.
name is eerily similar to `nixpkgs`, i.e. the monorepo that defines all packages and one of the underlying technologies here. i get the play on buildpacks, but still, as a nix user it makes me do a double take reading the name
this is neat though, and in political terms, the elevator pitch mentions nix itself as an implementation detail in passing. hopefully, if this catches on, it'll function as a non-threatening gateway drug to nix itself, when users inevitably go digging into the weeds
for anyone interested, prior art on the nix container front: https://nixery.dev
The similarity is definitely on purpose. It pulls ideals from both nixpkgs as well as the cloud native buildpacks.
Our goal is to make reproducibility more approachable/less cumbersome, and for people to be able to define additional nix/apt/etc packages added to the container .
You can add package you'd like via the environment variables NIXPACKS_PKGS (or NIXPACKS_APT_PKGS for apt)
It's interesting/neat, but it doesn't make use of e.g. nixpkgs' beautifully maintained python package distribution, leaving you with all the same pitfalls of pip/pypi as any other buildpack. And I don't imagine the end result will be a "pure" artefact, and so I can't see how they'd be able to safely remove the build-time packages from the final image.
Do you? I think they're referring to these packages[0].
For me `nixpacks plan .` seems to generate a plain old pip (or poetry) install. The problem with the python packaging ecosystem is that since the start, in order to figure out which dependencies a package has, we needed to run arbitrary python code (setup.py). I can write a package in 5 minutes that declares its deps with `random.choice`. If you're installing from pypi.org, then there's no reproducibility guarantee. Though, I know nothing about Nix or how it works, so maybe you're changing pip's index-url somewhere else..
You are correct in that Nixpacks uses the languages own package manager instead of installing all dependencies with Nix using tools like node2nix, cargo2nix, or poetry2nix. This was a choice as the main problems Nixpacks aims to solve are with system dependencies and getting an environment where your app can install and build with as little configuration as possible. While continuing to use the same tooling that app authors are likely using locally (e.g. npm, pip, cargo). We have found that nixifying all possible deps often creates situations where the app author must know that Nix is being used under the hood and modify their app accordingly. Nothing against these tools, but using them didn't provide the experience we set out to have. If a user of Nixpacks wants to override the install command to `do_something && npm install`, they should be able to without worrying about how it affects the Nix store.
However, any Nix package can be installed, including from python-packages.nix, and we are planning on allowing more Nix config and customization in the future.
Indeed, it's a compromise. Something I can't figure out though is if you make any attempt to remove unnecessary nix packages for the final image, i.e. in cases where a (language-)package has a native module built from source, can you generate a final image without gcc present?
There are a couple of sneaky ways this could possibly be done, but wondered if you explored any of them.
Having just written up some thoughts on how you could do it I realize they wouldn't work, because I imagine you're presenting a "standard"/FHS-esque view of the filesystem to the non-nix packages, i.e. they expect a `/usr/lib/libssl.so` to exist. Whereas Nix's ability to strip away runtime-unnecessary packages comes from packages only ever using explicit absolute references to each other.
I guess a "third way" might involve using e.g. pypi2nix, bundix, node2nix and the like.
It's in the same vein, but our goal is to create a standard for fully reproducible builds
Nixpacks can also spit out a plan (https://nixpacks.com/docs/how-it-works#plan) which, when run again, will assure that the build artifacts the exact same every time it's run against that plan.
The result is that any code that's built with nixpacks has all it's dependencies snapshotted so it won't break over time.
For us, that means anytime someone comes to our platform (http://railway.app/) and deploys an Template, it won't be broken
I don't like the docs / project description. Everything is made 'so simple' that important parts are now gone and everything is now pure magic, I guess.
> We want to make it more trivial to build OCI compliant images without all the knowledge required to understand how Dockerfiles/Container Filesystem/BuildLayers/etc work
As someone who understands how the above mentioned stuff works - I feel like I'm being forcefully excluded from the tool's target audience as I need explanations and the docs are written with such an idea in mind that it is something I shouldn't know.
> Analyze the app source directory and generates a reproducible build plan. This plan can be saved (in JSON format) and re-used at a later date to build the image in the exact same way every time.
How about an example of how a simple plan for a few projects would look like?
Plus, the tool seems to assume too much:
> To create the plan, language providers are matched against the app source directory and suggest Nix packages
That's the ideal! We want to make it more trivial to build OCI compliant images without all the knowledge required to understand how Dockerfiles/Container Filesystem/BuildLayers/etc work
BuildKit makes this possible and we've got lots of stuff planned. Would love to hear anything you can think of that would make this more friendly!
Personally I've been making OCI images using Nix, just by running tar, sha256sum and jq:
- We can get a Nix derivation's dependencies using `writeDependenciesToFile`
- We can put these dependencies (alongside anything else we like) into an OCI "layer" using GNU tar: the `--hard-dereference` and `--mode=755` options work well, and we can include directories using `-C /foo --filesFrom=foo.txt` where foo.txt comes from `cd /foo && find . -maxdepth 1`, with the leading `./` removed.
- A "digest" is just a JSON file like `{size: 123, digest: "abc"}`, e.g. generated using `stat` and `sha256sum`
- Those sha256sums can be referenced in a config.json, like `{"rootfs": {"type": "layers", "diff_ids": [...]}}`; along with the application-specific config (EntryPoint, WorkingDir, etc.)
- Finally, a "manifest" is just another JSON file; with `{"mediaType": "application/vnd.oci.image.config.v1+json"}` for the config digest, and `{"mediaType": "application/vnd.oci.image.layer.v1.tar+gzip"}` for each layer digest.
Unlike Docker, these commands can all be run directly (from the "host", although it's not really hosting anything, since we don't need any containers or VMs to run the above); their contents are completely declarative (since we're just zipping up pre-existing files, e.g. built/fetched by Nix; rather than mutating a filesystem in-place); we can do it on any OS (e.g. no need for a Linux VM on macOS); etc.