From b0634b6acc8faf74ff3646fa90ed783e6338ecf5 Mon Sep 17 00:00:00 2001 From: Tasko Olevski <16360283+olevski@users.noreply.github.com> Date: Tue, 7 Apr 2026 17:30:55 +0200 Subject: [PATCH 1/5] feat: add nix support --- buildpacks/nix-packages/bin/build | 22 ++++++++++++++++++++++ buildpacks/nix-packages/bin/detect | 17 +++++++++++++++++ buildpacks/nix-packages/buildpack.toml | 13 +++++++++++++ buildpacks/nix-packages/package.toml | 2 ++ extensions/nix/bin/detect | 10 ++++++++++ extensions/nix/bin/generate | 21 +++++++++++++++++++++ extensions/nix/extensions.toml | 7 +++++++ samples/nix/flake.nix | 24 ++++++++++++++++++++++++ 8 files changed, 116 insertions(+) create mode 100755 buildpacks/nix-packages/bin/build create mode 100755 buildpacks/nix-packages/bin/detect create mode 100644 buildpacks/nix-packages/buildpack.toml create mode 100644 buildpacks/nix-packages/package.toml create mode 100755 extensions/nix/bin/detect create mode 100755 extensions/nix/bin/generate create mode 100644 extensions/nix/extensions.toml create mode 100644 samples/nix/flake.nix diff --git a/buildpacks/nix-packages/bin/build b/buildpacks/nix-packages/bin/build new file mode 100755 index 0000000..31ab8a0 --- /dev/null +++ b/buildpacks/nix-packages/bin/build @@ -0,0 +1,22 @@ +#!/usr/bin/env bash +set -euo pipefail + +layers_dir="$1" + +nix_layer="$layers_dir/nix-packages" +mkdir -p "$nix_layer" + +# Build the flake's default package or a specific output +nix build --no-sandbox --out-link "$nix_layer/result" .# + +cat >"$nix_layer.toml" <"$env_dir/PATH.prepend" +echo -n ":" >"$env_dir/PATH.delim" diff --git a/buildpacks/nix-packages/bin/detect b/buildpacks/nix-packages/bin/detect new file mode 100755 index 0000000..a52fd28 --- /dev/null +++ b/buildpacks/nix-packages/bin/detect @@ -0,0 +1,17 @@ +#!/usr/bin/env bash + +if [[ ! -f flake.nix ]]; then + echo "Could nout find flake.nix, skipping the nix packages buildpack." + exit 1 +fi +echo "Found flake.nix, will apply the buildpack." + +cat >>"$CNB_BUILD_PLAN_PATH" <<'EOF' +[[requires]] + name = "nix" + +[[provides]] + name = "nix-packages" +EOF + +exit 0 diff --git a/buildpacks/nix-packages/buildpack.toml b/buildpacks/nix-packages/buildpack.toml new file mode 100644 index 0000000..820ed82 --- /dev/null +++ b/buildpacks/nix-packages/buildpack.toml @@ -0,0 +1,13 @@ +api = "0.11" +[buildpack] +id = "renku/nix-packages" +version = "0.3.1" +name = "Nix Packages Buildpack" + +[[targets]] +os = "linux" +arch = "amd64" + +[[targets]] +os = "linux" +arch = "arm64" diff --git a/buildpacks/nix-packages/package.toml b/buildpacks/nix-packages/package.toml new file mode 100644 index 0000000..54b0d2e --- /dev/null +++ b/buildpacks/nix-packages/package.toml @@ -0,0 +1,2 @@ +[buildpack] +uri = "." diff --git a/extensions/nix/bin/detect b/extensions/nix/bin/detect new file mode 100755 index 0000000..245912a --- /dev/null +++ b/extensions/nix/bin/detect @@ -0,0 +1,10 @@ +#!/usr/bin/env bash +set -euo pipefail + +plan_path=$CNB_BUILD_PLAN_PATH +echo "Nix install image extension" + +cat >>"${plan_path}" <"$output_dir/build.Dockerfile" <<'EOF' +ARG base_image +FROM ${base_image} +ARG user_id + +USER root +RUN curl -fsSL https://nixos.org/nix/install | sh -s -- --no-daemon +ENV PATH="/root/.nix-profile/bin:/nix/var/nix/profiles/default/bin:${PATH}" + +# Enable flakes +RUN mkdir -p /etc/nix && echo 'experimental-features = nix-command flakes' >> /etc/nix/nix.conf + +USER ${user_id} +EOF diff --git a/extensions/nix/extensions.toml b/extensions/nix/extensions.toml new file mode 100644 index 0000000..455b553 --- /dev/null +++ b/extensions/nix/extensions.toml @@ -0,0 +1,7 @@ +api = "0.11" + +[extension] +id = "renku/nix" +name = "Nix Installer" +version = "0.2.3" +description = "Installs Nix into the build image via image extension" diff --git a/samples/nix/flake.nix b/samples/nix/flake.nix new file mode 100644 index 0000000..e4ec20a --- /dev/null +++ b/samples/nix/flake.nix @@ -0,0 +1,24 @@ +{ + description = "A simple flake for testing the CNB nix buildpack"; + + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; + flake-utils.url = "github:numtide/flake-utils"; + }; + + outputs = { self, nixpkgs, flake-utils }: + flake-utils.lib.eachDefaultSystem (system: + let + pkgs = import nixpkgs { inherit system; }; + in + { + packages.default = pkgs.buildEnv { + name = "cns-nix-env"; + paths = [ + pkgs.cowsay + pkgs.fortune + ]; + }; + } + ); +} From b07dfd9a21ff4081687e53eb68be3ec669104736 Mon Sep 17 00:00:00 2001 From: Tasko Olevski <16360283+olevski@users.noreply.github.com> Date: Tue, 7 Apr 2026 17:33:25 +0200 Subject: [PATCH 2/5] chore: add flake lock file --- samples/nix/flake.lock | 61 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 samples/nix/flake.lock diff --git a/samples/nix/flake.lock b/samples/nix/flake.lock new file mode 100644 index 0000000..1cea3d7 --- /dev/null +++ b/samples/nix/flake.lock @@ -0,0 +1,61 @@ +{ + "nodes": { + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1731533236, + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1775549110, + "narHash": "sha256-gCXSLBI1drlFwYlNqqPS9cFXvraaEGyLS8Sq45p7b/Q=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "a3db02183b5da6fbf728203492a5d1b9d109b7f9", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} From 4c5fe85a5bfc14f4b97d6d6f308b03a69d1ca07b Mon Sep 17 00:00:00 2001 From: Tasko Olevski <16360283+olevski@users.noreply.github.com> Date: Tue, 7 Apr 2026 17:39:30 +0200 Subject: [PATCH 3/5] chore: add test for nix buildpack --- tests/e2e/buildpacks_test.go | 47 ++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/tests/e2e/buildpacks_test.go b/tests/e2e/buildpacks_test.go index add528f..b54d018 100644 --- a/tests/e2e/buildpacks_test.go +++ b/tests/e2e/buildpacks_test.go @@ -315,4 +315,51 @@ var _ = Describe("Testing samples", Label("samples"), Ordered, func() { }, Entry("using homebrew sample", "../../samples/homebrew"), ) + + DescribeTableSubtree( + "nix", + func(source string) { + var image string + var container string + var port int + BeforeAll(func(ctx SpecContext) { + image = strings.ToLower(fmt.Sprintf("test-image-%s", getULID())) + Expect(buildImage(ctx, builderImg, source, image, map[string]string{})).To(Succeed()) + port = getFreePortOrDie() + envVars := []string{fmt.Sprintf("RENKU_SESSION_PORT=%d", port)} + ports := map[int]int{port: port} + container, err = runImage(ctx, client, image, envVars, ports) + Expect(err).ToNot(HaveOccurred()) + }) + + AfterAll(func(ctx SpecContext) { + if container != "" && client != nil { + log.Println("Cleaning up container") + err = removeContainer(ctx, client, container) + if err != nil { + log.Println(err) + } + } + if image != "" && client != nil { + log.Println("Cleaning up image") + err = removeImage(ctx, client, image) + if err != nil { + log.Println(err) + } + } + }) + + Context("when the container is running", func() { + It("cowsay should exist in the container", func(ctx SpecContext) { + _, err := execInContainer(ctx, client, container, []string{"launcher", "cowsay", "moo"}) + Expect(err).ToNot(HaveOccurred()) + }) + It("nix should not exist in the container", func(ctx SpecContext) { + _, err := execInContainer(ctx, client, container, []string{"launcher", "nix"}) + Expect(err).To(HaveOccurred()) + }) + }) + }, + Entry("using nix sample", "../../samples/nix"), + ) }) From 80801e3d823ce4b31869ee9b3de2ae02c5a01044 Mon Sep 17 00:00:00 2001 From: Tasko Olevski <16360283+olevski@users.noreply.github.com> Date: Tue, 7 Apr 2026 17:49:56 +0200 Subject: [PATCH 4/5] chore: update builders specs --- builders/cuda-selector/builder.toml | 34 +++++++++++++++++++++++++++-- builders/selector/builder.toml | 34 +++++++++++++++++++++++++++-- extensions/nix/extensions.toml | 2 +- extensions/r-deps/extension.toml | 2 +- 4 files changed, 66 insertions(+), 6 deletions(-) diff --git a/builders/cuda-selector/builder.toml b/builders/cuda-selector/builder.toml index 0b303be..f00bb24 100644 --- a/builders/cuda-selector/builder.toml +++ b/builders/cuda-selector/builder.toml @@ -70,9 +70,17 @@ value = "vscodium" uri = "../../buildpacks/homebrew" version = "0.3.1" +[[buildpacks]] + uri = "../../buildpacks/nix-packages" + version = "0.3.1" + [[extensions]] uri="../../extensions/r-deps" - version= "0.2.3" + version= "0.3.1" + +[[extensions]] + uri="../../extensions/nix" + version= "0.3.1" [lifecycle] version = "0.20.19" @@ -113,6 +121,10 @@ value = "vscodium" id = "renku/init-scripts" version = "0.3.1" optional = true + [[order.group]] + id = "renku/nix-packages" + version = "0.3.1" + optional = true [[order]] [[order.group]] @@ -143,6 +155,10 @@ value = "vscodium" id = "renku/init-scripts" version = "0.3.1" optional = true + [[order.group]] + id = "renku/nix-packages" + version = "0.3.1" + optional = true [[order]] @@ -174,6 +190,10 @@ value = "vscodium" id = "renku/init-scripts" version = "0.3.1" optional = true + [[order.group]] + id = "renku/nix-packages" + version = "0.3.1" + optional = true [[order]] @@ -204,11 +224,21 @@ value = "vscodium" id = "renku/init-scripts" version = "0.3.1" optional = true + [[order.group]] + id = "renku/nix-packages" + version = "0.3.1" + optional = true [[order-extensions]] [[order-extensions.group]] id = "renku/r-deps" - version = "0.2.3" + version = "0.3.1" + optional = true + +[[order-extensions]] +[[order-extensions.group]] + id = "renku/nix" + version = "0.3.1" optional = true [build] diff --git a/builders/selector/builder.toml b/builders/selector/builder.toml index c8eb24d..b49700b 100644 --- a/builders/selector/builder.toml +++ b/builders/selector/builder.toml @@ -66,13 +66,21 @@ value = "vscodium" uri = "docker://docker.io/heroku/buildpack-deb-packages:0.2.0" version = "0.2.0" +[[buildpacks]] + uri = "../../buildpacks/nix-packages" + version = "0.3.1" + [[buildpacks]] uri = "../../buildpacks/homebrew" version = "0.3.1" [[extensions]] uri="../../extensions/r-deps" - version= "0.2.3" + version= "0.3.1" + +[[extensions]] + uri="../../extensions/nix" + version= "0.3.1" [lifecycle] version = "0.20.19" @@ -113,6 +121,10 @@ value = "vscodium" id = "renku/init-scripts" version = "0.3.1" optional = true + [[order.group]] + id = "renku/nix-packages" + version = "0.3.1" + optional = true [[order]] [[order.group]] @@ -143,6 +155,10 @@ value = "vscodium" id = "renku/init-scripts" version = "0.3.1" optional = true + [[order.group]] + id = "renku/nix-packages" + version = "0.3.1" + optional = true [[order]] @@ -174,6 +190,10 @@ value = "vscodium" id = "renku/init-scripts" version = "0.3.1" optional = true + [[order.group]] + id = "renku/nix-packages" + version = "0.3.1" + optional = true [[order]] @@ -204,11 +224,21 @@ value = "vscodium" id = "renku/init-scripts" version = "0.3.1" optional = true + [[order.group]] + id = "renku/nix-packages" + version = "0.3.1" + optional = true [[order-extensions]] [[order-extensions.group]] id = "renku/r-deps" - version = "0.2.3" + version = "0.3.1" + optional = true + +[[order-extensions]] +[[order-extensions.group]] + id = "renku/nix" + version = "0.3.1" optional = true [build] diff --git a/extensions/nix/extensions.toml b/extensions/nix/extensions.toml index 455b553..1ecd56e 100644 --- a/extensions/nix/extensions.toml +++ b/extensions/nix/extensions.toml @@ -3,5 +3,5 @@ api = "0.11" [extension] id = "renku/nix" name = "Nix Installer" -version = "0.2.3" +version = "0.3.1" description = "Installs Nix into the build image via image extension" diff --git a/extensions/r-deps/extension.toml b/extensions/r-deps/extension.toml index 032bb85..991d9f8 100644 --- a/extensions/r-deps/extension.toml +++ b/extensions/r-deps/extension.toml @@ -2,5 +2,5 @@ api = "0.11" [extension] id = "renku/r-deps" -version = "0.2.3" +version = "0.3.1" name = "R dependencies" From 2f4e204a187784dad6e333b418421f4eb1dedfa3 Mon Sep 17 00:00:00 2001 From: Tasko Olevski <16360283+olevski@users.noreply.github.com> Date: Wed, 8 Apr 2026 00:50:57 +0200 Subject: [PATCH 5/5] symlink ping-pong --- builders/selector/builder.toml | 4 +-- buildpacks/nix-packages/bin/build | 28 +++++++++++-------- buildpacks/nix-packages/bin/detect | 3 ++ extensions/nix/bin/generate | 27 ++++++++++++++++-- .../nix/{extensions.toml => extension.toml} | 0 samples/nix/requirements.txt | 1 + 6 files changed, 47 insertions(+), 16 deletions(-) rename extensions/nix/{extensions.toml => extension.toml} (100%) create mode 100644 samples/nix/requirements.txt diff --git a/builders/selector/builder.toml b/builders/selector/builder.toml index b49700b..3cf04c8 100644 --- a/builders/selector/builder.toml +++ b/builders/selector/builder.toml @@ -231,13 +231,13 @@ value = "vscodium" [[order-extensions]] [[order-extensions.group]] - id = "renku/r-deps" + id = "renku/nix" version = "0.3.1" optional = true [[order-extensions]] [[order-extensions.group]] - id = "renku/nix" + id = "renku/r-deps" version = "0.3.1" optional = true diff --git a/buildpacks/nix-packages/bin/build b/buildpacks/nix-packages/bin/build index 31ab8a0..a9821b1 100755 --- a/buildpacks/nix-packages/bin/build +++ b/buildpacks/nix-packages/bin/build @@ -4,19 +4,25 @@ set -euo pipefail layers_dir="$1" nix_layer="$layers_dir/nix-packages" -mkdir -p "$nix_layer" +mkdir -p "$nix_layer/nix/store" -# Build the flake's default package or a specific output -nix build --no-sandbox --out-link "$nix_layer/result" .# +store_path=$(nix build --no-sandbox --no-link --print-out-paths .#) + +echo "---> Store path: $store_path" + +# Copy the entire closure into the layer with real files (no symlinks) +while IFS= read -r path; do + echo "---> Copying $path" + cp -rL "$path" "$nix_layer/nix/store/" +done < <(nix path-info --no-sandbox --recursive .#) cat >"$nix_layer.toml" <"$env_dir/PATH.prepend" -echo -n ":" >"$env_dir/PATH.delim" +mkdir -p "$nix_layer/env.launch" +printf '%s' "${store_path}/bin" >"$nix_layer/env.launch/PATH.prepend" +printf '%s' ":" >"$nix_layer/env.launch/PATH.delim" diff --git a/buildpacks/nix-packages/bin/detect b/buildpacks/nix-packages/bin/detect index a52fd28..1775547 100755 --- a/buildpacks/nix-packages/bin/detect +++ b/buildpacks/nix-packages/bin/detect @@ -10,6 +10,9 @@ cat >>"$CNB_BUILD_PLAN_PATH" <<'EOF' [[requires]] name = "nix" +[[requires]] + name = "nix-packages" + [[provides]] name = "nix-packages" EOF diff --git a/extensions/nix/bin/generate b/extensions/nix/bin/generate index 9a5d8c9..e7ef304 100755 --- a/extensions/nix/bin/generate +++ b/extensions/nix/bin/generate @@ -11,11 +11,32 @@ FROM ${base_image} ARG user_id USER root -RUN curl -fsSL https://nixos.org/nix/install | sh -s -- --no-daemon -ENV PATH="/root/.nix-profile/bin:/nix/var/nix/profiles/default/bin:${PATH}" +RUN mkdir -p /nix && chmod 755 /nix +RUN curl -fsSL https://install.determinate.systems/nix | \ + sh -s -- install linux \ + --no-confirm \ + --init none \ + --extra-conf "sandbox = false" \ + ${NIX_VERSION:+--nix-package-url "https://releases.nixos.org/nix/nix-${NIX_VERSION}/nix-${NIX_VERSION}-$(uname -m)-linux.tar.xz"} +# ENV PATH="/nix/var/nix/profiles/default/bin:\${PATH}" +ENV PATH="${PATH}:/nix/var/nix/profiles/default/bin" # Enable flakes -RUN mkdir -p /etc/nix && echo 'experimental-features = nix-command flakes' >> /etc/nix/nix.conf +RUN /bin/mkdir -p /etc/nix && echo 'experimental-features = nix-command flakes' >> /etc/nix/nix.conf +RUN chown -R ${CNB_USER_ID}:${CNB_GROUP_ID} /nix USER ${user_id} EOF + +BUILDPACK_ID="renku_nix-packages" +LAYER_NAME="nix-packages" +LAYER_PATH="/layers/${BUILDPACK_ID}/${LAYER_NAME}/nix/store" + +cat >"$output_dir/run.Dockerfile" <