ZFS backend: support pyxis (Slurm) workflows where enroot runs after privilege-drop to the job user #9

Description

@sodre

User story

As an HPC operator running NVIDIA pyxis on Slurm with the zenroot ZFS backend, I want pyxis-launched containers (srun --container-image=...) to use the ZFS backend's clone-on-create path, so that container startup is near-instant (zfs clone) instead of going through a per-job mksquashfs rebuild.

Today the ZFS backend works perfectly when invoked as root, but breaks for the pyxis path because pyxis correctly drops to the user's UID before invoking enroot create, and unprivileged enroot create blocks for ten minutes on template extraction (the documented mount(2) limitation in doc/zfs.md).

Problem statement

doc/zfs.md flags this:

Linux mount(2) limitation: Unprivileged users cannot call mount(2) even with ZFS delegation, so datasets won't auto-mount. Solutions include running enroot create as root or using user namespaces with fs.namespace.unprivileged_userns_clone=1.

In the documented Slurm + pyxis topology this isn't a workaround anyone can apply at the calling layer:

  • slurmstepd starts as root, then drops to the job user (uid 1000 in the repro below) before invoking enroot import / enroot create via the pyxis SPANK plugin.
  • That is pyxis's correct security posture; container processes run as the submitter, not root.
  • kernel.unprivileged_userns_clone=1 is already set on the host, but enroot create still hangs ten minutes on Timed out waiting for template extraction.
  • The result: pyxis fails with error: Failed to invoke spank plugin stack, ten minutes after job launch, on every container job.

This is a real-world blocker on a documented integration target (the upstream NVIDIA/enroot README points at pyxis as the canonical Slurm story).

What should work

After whatever changes are needed, the following should succeed end-to-end on a node where the ZFS backend is configured per doc/zfs.md and pyxis is installed normally:

# Submitter is unprivileged (uid 1000); slurmstepd starts root and drops.
srun --gres=gpu:1 --container-image=ubuntu:24.04 cat /etc/os-release

Acceptance criteria:

  1. The above completes in seconds on first run (template extracted), and near-instant on subsequent runs (clone-on-create benefit visible to the operator).
  2. Direct enroot create <foo>.sqsh run as the unprivileged submitter (no sudo) outside Slurm also succeeds.
  3. The unprivileged path does not require operator-managed wrappers, suid binaries, or AppArmor profile churn beyond what doc/zfs.md prescribes.
  4. Documentation in doc/zfs.md covers the Slurm + pyxis setup explicitly: any required sysctls, dataset properties, ZFS delegations, and (if applicable) the user-namespace plumbing that makes the unprivileged path work.

How the maintainer satisfies these (user-namespace mount delegation, suid enroot-mount helper, an opt-in "pre-mount on the operator side" path, etc.) is out of scope for this issue.

Repro environment

  • 3-node cluster: spark-ctrl (Pi 5, Debian 13) runs slurmctld + slurmdbd + MariaDB; two DGX Sparks (spark-f2ff, spark-4c55, Ubuntu 24.04, GB10 / arm64) run slurmd.
  • Slurm 25.11.5, built from source; auth/slurm + cred/slurm; SlurmUser=slurm uniform UID 64030.
  • pyxis v0.23.0, built against the locally-installed Slurm headers; loaded via plugstack.conf:
    required /usr/local/lib/slurm/spank_pyxis.so runtime_path=/var/lib/pyxis-runtime
    
  • enroot packages: enroot_4.1.2.zfs.1-1_arm64.deb and enroot+caps_4.1.2.zfs.1-1_arm64.deb (this fork, latest release).
  • ZFS layout per doc/zfs.md:
    tank/enroot/data mountpoint=/var/lib/enroot
    tank/enroot/data/.templates # auto-created by enroot
    
  • ZFS delegation:
    zfs allow sodre create,mount,clone,destroy,snapshot,rename,hold,release tank/enroot/data
    zfs allow sodre receive tank/enroot/data/.templates
    
  • /etc/enroot/enroot.conf:
    ENROOT_STORAGE_BACKEND zfs
    ENROOT_CACHE_PATH /var/cache/enroot
    ENROOT_DATA_PATH /var/lib/enroot
    ENROOT_TEMP_PATH /var/tmp/enroot
    ENROOT_RUNTIME_PATH /run/enroot
    ENROOT_SQUASH_OPTIONS -comp lzo -noD -processors 20
    
  • Sysctl: kernel.unprivileged_userns_clone = 1.
  • subuid/subgid: sodre:100000:65536.

Reproducer

Works (as root)

$ sudo enroot import -o /tmp/hello.sqsh docker://hello-world # ~0.6 s
$ sudo enroot create --name test-hw /tmp/hello.sqsh # ~0.3 s
$ sudo enroot create --name test-hw2 /tmp/hello.sqsh # ~0.3 s — clone-on-create
$ sudo enroot list
test-hw
test-hw2
$ sudo zfs list -r tank/enroot/data
NAME USED AVAIL REFER
tank/enroot/data 348K 2.65T 104K
tank/enroot/data/.templates 236K 2.65T 128K
tank/enroot/data/.templates/812f6e818527af65ea91417ba89e1ffe4a5b73409169fced5c073b636f278fac 108K 2.65T 108K
tank/enroot/data/test-hw 8K 2.65T 108K
tank/enroot/data/test-hw2 8K 2.65T 108K

Hangs (as the unprivileged submitter)

$ enroot import -o /tmp/hello.sqsh docker://hello-world # OK
$ time enroot create --name test-hw /tmp/hello.sqsh
[ERROR] Timed out waiting for template extraction:
tank/enroot/data/.templates/<sha>.
A previous extractor may have crashed; remove tank/enroot/data/.templates/<sha>.tmp
manually and retry.
real 10m2.759s

No .tmp file is left behind. The dataset tank/enroot/data/.templates/<sha> is created but appears empty / unmounted from the user's perspective. ZFS perms include mount, but mount(2) is rejected by the kernel (unrelated to ZFS delegation).

Hangs the same way under pyxis

$ srun --gres=gpu:1 --container-image=ubuntu:24.04 cat /etc/os-release
[2026-04-29T13:28:16] error: Failed to invoke spank plugin stack
srun: error: spark-4c55: task 0: Exited with exit code 1
real 10m11.650s

slurmstepd's enroot create invocation runs as uid 1000 (post-privilege-drop) and hits the same template-extraction timeout.

Out of scope for this ticket

  • Choice of solution. User namespaces, a small suid helper, an operator-side template-warm daemon, an opt-in flag that pre-mounts as root then chowns — all reasonable; deciding between them is a design question for this fork.
  • Whether to keep the dir backend as the default (it should — see doc/zfs.md's explicit "opt-in" framing).
  • pyxis-side configuration. This is purely about making enroot's ZFS backend usable from an unprivileged caller in the standard way pyxis invokes it.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions

      , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
       blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
      }
      } catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
      })();
      (function(){
      try {
      var __m = "github.com";
      var __re = new RegExp('^' + "github\\.com" + '
      
      Skip to content

      ZFS backend: support pyxis (Slurm) workflows where enroot runs after privilege-drop to the job user #9

      Description

      @sodre

      User story

      As an HPC operator running NVIDIA pyxis on Slurm with the zenroot ZFS backend, I want pyxis-launched containers (srun --container-image=...) to use the ZFS backend's clone-on-create path, so that container startup is near-instant (zfs clone) instead of going through a per-job mksquashfs rebuild.

      Today the ZFS backend works perfectly when invoked as root, but breaks for the pyxis path because pyxis correctly drops to the user's UID before invoking enroot create, and unprivileged enroot create blocks for ten minutes on template extraction (the documented mount(2) limitation in doc/zfs.md).

      Problem statement

      doc/zfs.md flags this:

      Linux mount(2) limitation: Unprivileged users cannot call mount(2) even with ZFS delegation, so datasets won't auto-mount. Solutions include running enroot create as root or using user namespaces with fs.namespace.unprivileged_userns_clone=1.

      In the documented Slurm + pyxis topology this isn't a workaround anyone can apply at the calling layer:

      • slurmstepd starts as root, then drops to the job user (uid 1000 in the repro below) before invoking enroot import / enroot create via the pyxis SPANK plugin.
      • That is pyxis's correct security posture; container processes run as the submitter, not root.
      • kernel.unprivileged_userns_clone=1 is already set on the host, but enroot create still hangs ten minutes on Timed out waiting for template extraction.
      • The result: pyxis fails with error: Failed to invoke spank plugin stack, ten minutes after job launch, on every container job.

      This is a real-world blocker on a documented integration target (the upstream NVIDIA/enroot README points at pyxis as the canonical Slurm story).

      What should work

      After whatever changes are needed, the following should succeed end-to-end on a node where the ZFS backend is configured per doc/zfs.md and pyxis is installed normally:

      # Submitter is unprivileged (uid 1000); slurmstepd starts root and drops.
      srun --gres=gpu:1 --container-image=ubuntu:24.04 cat /etc/os-release

      Acceptance criteria:

      1. The above completes in seconds on first run (template extracted), and near-instant on subsequent runs (clone-on-create benefit visible to the operator).
      2. Direct enroot create <foo>.sqsh run as the unprivileged submitter (no sudo) outside Slurm also succeeds.
      3. The unprivileged path does not require operator-managed wrappers, suid binaries, or AppArmor profile churn beyond what doc/zfs.md prescribes.
      4. Documentation in doc/zfs.md covers the Slurm + pyxis setup explicitly: any required sysctls, dataset properties, ZFS delegations, and (if applicable) the user-namespace plumbing that makes the unprivileged path work.

      How the maintainer satisfies these (user-namespace mount delegation, suid enroot-mount helper, an opt-in "pre-mount on the operator side" path, etc.) is out of scope for this issue.

      Repro environment

      • 3-node cluster: spark-ctrl (Pi 5, Debian 13) runs slurmctld + slurmdbd + MariaDB; two DGX Sparks (spark-f2ff, spark-4c55, Ubuntu 24.04, GB10 / arm64) run slurmd.
      • Slurm 25.11.5, built from source; auth/slurm + cred/slurm; SlurmUser=slurm uniform UID 64030.
      • pyxis v0.23.0, built against the locally-installed Slurm headers; loaded via plugstack.conf:
        required /usr/local/lib/slurm/spank_pyxis.so runtime_path=/var/lib/pyxis-runtime
        
      • enroot packages: enroot_4.1.2.zfs.1-1_arm64.deb and enroot+caps_4.1.2.zfs.1-1_arm64.deb (this fork, latest release).
      • ZFS layout per doc/zfs.md:
        tank/enroot/data mountpoint=/var/lib/enroot
        tank/enroot/data/.templates # auto-created by enroot
        
      • ZFS delegation:
        zfs allow sodre create,mount,clone,destroy,snapshot,rename,hold,release tank/enroot/data
        zfs allow sodre receive tank/enroot/data/.templates
        
      • /etc/enroot/enroot.conf:
        ENROOT_STORAGE_BACKEND zfs
        ENROOT_CACHE_PATH /var/cache/enroot
        ENROOT_DATA_PATH /var/lib/enroot
        ENROOT_TEMP_PATH /var/tmp/enroot
        ENROOT_RUNTIME_PATH /run/enroot
        ENROOT_SQUASH_OPTIONS -comp lzo -noD -processors 20
        
      • Sysctl: kernel.unprivileged_userns_clone = 1.
      • subuid/subgid: sodre:100000:65536.

      Reproducer

      Works (as root)

      $ sudo enroot import -o /tmp/hello.sqsh docker://hello-world # ~0.6 s
      $ sudo enroot create --name test-hw /tmp/hello.sqsh # ~0.3 s
      $ sudo enroot create --name test-hw2 /tmp/hello.sqsh # ~0.3 s — clone-on-create
      $ sudo enroot list
      test-hw
      test-hw2
      $ sudo zfs list -r tank/enroot/data
      NAME USED AVAIL REFER
      tank/enroot/data 348K 2.65T 104K
      tank/enroot/data/.templates 236K 2.65T 128K
      tank/enroot/data/.templates/812f6e818527af65ea91417ba89e1ffe4a5b73409169fced5c073b636f278fac 108K 2.65T 108K
      tank/enroot/data/test-hw 8K 2.65T 108K
      tank/enroot/data/test-hw2 8K 2.65T 108K

      Hangs (as the unprivileged submitter)

      $ enroot import -o /tmp/hello.sqsh docker://hello-world # OK
      $ time enroot create --name test-hw /tmp/hello.sqsh
      [ERROR] Timed out waiting for template extraction:
      tank/enroot/data/.templates/<sha>.
      A previous extractor may have crashed; remove tank/enroot/data/.templates/<sha>.tmp
      manually and retry.
      real 10m2.759s

      No .tmp file is left behind. The dataset tank/enroot/data/.templates/<sha> is created but appears empty / unmounted from the user's perspective. ZFS perms include mount, but mount(2) is rejected by the kernel (unrelated to ZFS delegation).

      Hangs the same way under pyxis

      $ srun --gres=gpu:1 --container-image=ubuntu:24.04 cat /etc/os-release
      [2026-04-29T13:28:16] error: Failed to invoke spank plugin stack
      srun: error: spark-4c55: task 0: Exited with exit code 1
      real 10m11.650s

      slurmstepd's enroot create invocation runs as uid 1000 (post-privilege-drop) and hits the same template-extraction timeout.

      Out of scope for this ticket

      • Choice of solution. User namespaces, a small suid helper, an operator-side template-warm daemon, an opt-in flag that pre-mounts as root then chowns — all reasonable; deciding between them is a design question for this fork.
      • Whether to keep the dir backend as the default (it should — see doc/zfs.md's explicit "opt-in" framing).
      • pyxis-side configuration. This is purely about making enroot's ZFS backend usable from an unprivileged caller in the standard way pyxis invokes it.

      Activity

      Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

      Metadata

      Metadata

      Assignees

      No one assigned

        Labels

        No labels
        No labels

        Type

        No type

        Projects

        No projects

          Milestone

          No milestone

          Relationships

          None yet

          Development

          No branches or pull requests

          Issue actions

          , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
          Skip to content

          ZFS backend: support pyxis (Slurm) workflows where enroot runs after privilege-drop to the job user #9

          Description

          @sodre

          User story

          As an HPC operator running NVIDIA pyxis on Slurm with the zenroot ZFS backend, I want pyxis-launched containers (srun --container-image=...) to use the ZFS backend's clone-on-create path, so that container startup is near-instant (zfs clone) instead of going through a per-job mksquashfs rebuild.

          Today the ZFS backend works perfectly when invoked as root, but breaks for the pyxis path because pyxis correctly drops to the user's UID before invoking enroot create, and unprivileged enroot create blocks for ten minutes on template extraction (the documented mount(2) limitation in doc/zfs.md).

          Problem statement

          doc/zfs.md flags this:

          Linux mount(2) limitation: Unprivileged users cannot call mount(2) even with ZFS delegation, so datasets won't auto-mount. Solutions include running enroot create as root or using user namespaces with fs.namespace.unprivileged_userns_clone=1.

          In the documented Slurm + pyxis topology this isn't a workaround anyone can apply at the calling layer:

          • slurmstepd starts as root, then drops to the job user (uid 1000 in the repro below) before invoking enroot import / enroot create via the pyxis SPANK plugin.
          • That is pyxis's correct security posture; container processes run as the submitter, not root.
          • kernel.unprivileged_userns_clone=1 is already set on the host, but enroot create still hangs ten minutes on Timed out waiting for template extraction.
          • The result: pyxis fails with error: Failed to invoke spank plugin stack, ten minutes after job launch, on every container job.

          This is a real-world blocker on a documented integration target (the upstream NVIDIA/enroot README points at pyxis as the canonical Slurm story).

          What should work

          After whatever changes are needed, the following should succeed end-to-end on a node where the ZFS backend is configured per doc/zfs.md and pyxis is installed normally:

          # Submitter is unprivileged (uid 1000); slurmstepd starts root and drops.
          srun --gres=gpu:1 --container-image=ubuntu:24.04 cat /etc/os-release

          Acceptance criteria:

          1. The above completes in seconds on first run (template extracted), and near-instant on subsequent runs (clone-on-create benefit visible to the operator).
          2. Direct enroot create <foo>.sqsh run as the unprivileged submitter (no sudo) outside Slurm also succeeds.
          3. The unprivileged path does not require operator-managed wrappers, suid binaries, or AppArmor profile churn beyond what doc/zfs.md prescribes.
          4. Documentation in doc/zfs.md covers the Slurm + pyxis setup explicitly: any required sysctls, dataset properties, ZFS delegations, and (if applicable) the user-namespace plumbing that makes the unprivileged path work.

          How the maintainer satisfies these (user-namespace mount delegation, suid enroot-mount helper, an opt-in "pre-mount on the operator side" path, etc.) is out of scope for this issue.

          Repro environment

          • 3-node cluster: spark-ctrl (Pi 5, Debian 13) runs slurmctld + slurmdbd + MariaDB; two DGX Sparks (spark-f2ff, spark-4c55, Ubuntu 24.04, GB10 / arm64) run slurmd.
          • Slurm 25.11.5, built from source; auth/slurm + cred/slurm; SlurmUser=slurm uniform UID 64030.
          • pyxis v0.23.0, built against the locally-installed Slurm headers; loaded via plugstack.conf:
            required /usr/local/lib/slurm/spank_pyxis.so runtime_path=/var/lib/pyxis-runtime
            
          • enroot packages: enroot_4.1.2.zfs.1-1_arm64.deb and enroot+caps_4.1.2.zfs.1-1_arm64.deb (this fork, latest release).
          • ZFS layout per doc/zfs.md:
            tank/enroot/data mountpoint=/var/lib/enroot
            tank/enroot/data/.templates # auto-created by enroot
            
          • ZFS delegation:
            zfs allow sodre create,mount,clone,destroy,snapshot,rename,hold,release tank/enroot/data
            zfs allow sodre receive tank/enroot/data/.templates
            
          • /etc/enroot/enroot.conf:
            ENROOT_STORAGE_BACKEND zfs
            ENROOT_CACHE_PATH /var/cache/enroot
            ENROOT_DATA_PATH /var/lib/enroot
            ENROOT_TEMP_PATH /var/tmp/enroot
            ENROOT_RUNTIME_PATH /run/enroot
            ENROOT_SQUASH_OPTIONS -comp lzo -noD -processors 20
            
          • Sysctl: kernel.unprivileged_userns_clone = 1.
          • subuid/subgid: sodre:100000:65536.

          Reproducer

          Works (as root)

          $ sudo enroot import -o /tmp/hello.sqsh docker://hello-world # ~0.6 s
          $ sudo enroot create --name test-hw /tmp/hello.sqsh # ~0.3 s
          $ sudo enroot create --name test-hw2 /tmp/hello.sqsh # ~0.3 s — clone-on-create
          $ sudo enroot list
          test-hw
          test-hw2
          $ sudo zfs list -r tank/enroot/data
          NAME USED AVAIL REFER
          tank/enroot/data 348K 2.65T 104K
          tank/enroot/data/.templates 236K 2.65T 128K
          tank/enroot/data/.templates/812f6e818527af65ea91417ba89e1ffe4a5b73409169fced5c073b636f278fac 108K 2.65T 108K
          tank/enroot/data/test-hw 8K 2.65T 108K
          tank/enroot/data/test-hw2 8K 2.65T 108K

          Hangs (as the unprivileged submitter)

          $ enroot import -o /tmp/hello.sqsh docker://hello-world # OK
          $ time enroot create --name test-hw /tmp/hello.sqsh
          [ERROR] Timed out waiting for template extraction:
          tank/enroot/data/.templates/<sha>.
          A previous extractor may have crashed; remove tank/enroot/data/.templates/<sha>.tmp
          manually and retry.
          real 10m2.759s

          No .tmp file is left behind. The dataset tank/enroot/data/.templates/<sha> is created but appears empty / unmounted from the user's perspective. ZFS perms include mount, but mount(2) is rejected by the kernel (unrelated to ZFS delegation).

          Hangs the same way under pyxis

          $ srun --gres=gpu:1 --container-image=ubuntu:24.04 cat /etc/os-release
          [2026-04-29T13:28:16] error: Failed to invoke spank plugin stack
          srun: error: spark-4c55: task 0: Exited with exit code 1
          real 10m11.650s

          slurmstepd's enroot create invocation runs as uid 1000 (post-privilege-drop) and hits the same template-extraction timeout.

          Out of scope for this ticket

          • Choice of solution. User namespaces, a small suid helper, an operator-side template-warm daemon, an opt-in flag that pre-mounts as root then chowns — all reasonable; deciding between them is a design question for this fork.
          • Whether to keep the dir backend as the default (it should — see doc/zfs.md's explicit "opt-in" framing).
          • pyxis-side configuration. This is purely about making enroot's ZFS backend usable from an unprivileged caller in the standard way pyxis invokes it.

          Activity

          Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

          Metadata

          Metadata

          Assignees

          No one assigned

            Labels

            No labels
            No labels

            Type

            No type

            Projects

            No projects

              Milestone

              No milestone

              Relationships

              None yet

              Development

              No branches or pull requests

              Issue actions

              , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
              Skip to content

              ZFS backend: support pyxis (Slurm) workflows where enroot runs after privilege-drop to the job user #9

              Description

              @sodre

              User story

              As an HPC operator running NVIDIA pyxis on Slurm with the zenroot ZFS backend, I want pyxis-launched containers (srun --container-image=...) to use the ZFS backend's clone-on-create path, so that container startup is near-instant (zfs clone) instead of going through a per-job mksquashfs rebuild.

              Today the ZFS backend works perfectly when invoked as root, but breaks for the pyxis path because pyxis correctly drops to the user's UID before invoking enroot create, and unprivileged enroot create blocks for ten minutes on template extraction (the documented mount(2) limitation in doc/zfs.md).

              Problem statement

              doc/zfs.md flags this:

              Linux mount(2) limitation: Unprivileged users cannot call mount(2) even with ZFS delegation, so datasets won't auto-mount. Solutions include running enroot create as root or using user namespaces with fs.namespace.unprivileged_userns_clone=1.

              In the documented Slurm + pyxis topology this isn't a workaround anyone can apply at the calling layer:

              • slurmstepd starts as root, then drops to the job user (uid 1000 in the repro below) before invoking enroot import / enroot create via the pyxis SPANK plugin.
              • That is pyxis's correct security posture; container processes run as the submitter, not root.
              • kernel.unprivileged_userns_clone=1 is already set on the host, but enroot create still hangs ten minutes on Timed out waiting for template extraction.
              • The result: pyxis fails with error: Failed to invoke spank plugin stack, ten minutes after job launch, on every container job.

              This is a real-world blocker on a documented integration target (the upstream NVIDIA/enroot README points at pyxis as the canonical Slurm story).

              What should work

              After whatever changes are needed, the following should succeed end-to-end on a node where the ZFS backend is configured per doc/zfs.md and pyxis is installed normally:

              # Submitter is unprivileged (uid 1000); slurmstepd starts root and drops.
              srun --gres=gpu:1 --container-image=ubuntu:24.04 cat /etc/os-release

              Acceptance criteria:

              1. The above completes in seconds on first run (template extracted), and near-instant on subsequent runs (clone-on-create benefit visible to the operator).
              2. Direct enroot create <foo>.sqsh run as the unprivileged submitter (no sudo) outside Slurm also succeeds.
              3. The unprivileged path does not require operator-managed wrappers, suid binaries, or AppArmor profile churn beyond what doc/zfs.md prescribes.
              4. Documentation in doc/zfs.md covers the Slurm + pyxis setup explicitly: any required sysctls, dataset properties, ZFS delegations, and (if applicable) the user-namespace plumbing that makes the unprivileged path work.

              How the maintainer satisfies these (user-namespace mount delegation, suid enroot-mount helper, an opt-in "pre-mount on the operator side" path, etc.) is out of scope for this issue.

              Repro environment

              • 3-node cluster: spark-ctrl (Pi 5, Debian 13) runs slurmctld + slurmdbd + MariaDB; two DGX Sparks (spark-f2ff, spark-4c55, Ubuntu 24.04, GB10 / arm64) run slurmd.
              • Slurm 25.11.5, built from source; auth/slurm + cred/slurm; SlurmUser=slurm uniform UID 64030.
              • pyxis v0.23.0, built against the locally-installed Slurm headers; loaded via plugstack.conf:
                required /usr/local/lib/slurm/spank_pyxis.so runtime_path=/var/lib/pyxis-runtime
                
              • enroot packages: enroot_4.1.2.zfs.1-1_arm64.deb and enroot+caps_4.1.2.zfs.1-1_arm64.deb (this fork, latest release).
              • ZFS layout per doc/zfs.md:
                tank/enroot/data mountpoint=/var/lib/enroot
                tank/enroot/data/.templates # auto-created by enroot
                
              • ZFS delegation:
                zfs allow sodre create,mount,clone,destroy,snapshot,rename,hold,release tank/enroot/data
                zfs allow sodre receive tank/enroot/data/.templates
                
              • /etc/enroot/enroot.conf:
                ENROOT_STORAGE_BACKEND zfs
                ENROOT_CACHE_PATH /var/cache/enroot
                ENROOT_DATA_PATH /var/lib/enroot
                ENROOT_TEMP_PATH /var/tmp/enroot
                ENROOT_RUNTIME_PATH /run/enroot
                ENROOT_SQUASH_OPTIONS -comp lzo -noD -processors 20
                
              • Sysctl: kernel.unprivileged_userns_clone = 1.
              • subuid/subgid: sodre:100000:65536.

              Reproducer

              Works (as root)

              $ sudo enroot import -o /tmp/hello.sqsh docker://hello-world # ~0.6 s
              $ sudo enroot create --name test-hw /tmp/hello.sqsh # ~0.3 s
              $ sudo enroot create --name test-hw2 /tmp/hello.sqsh # ~0.3 s — clone-on-create
              $ sudo enroot list
              test-hw
              test-hw2
              $ sudo zfs list -r tank/enroot/data
              NAME USED AVAIL REFER
              tank/enroot/data 348K 2.65T 104K
              tank/enroot/data/.templates 236K 2.65T 128K
              tank/enroot/data/.templates/812f6e818527af65ea91417ba89e1ffe4a5b73409169fced5c073b636f278fac 108K 2.65T 108K
              tank/enroot/data/test-hw 8K 2.65T 108K
              tank/enroot/data/test-hw2 8K 2.65T 108K

              Hangs (as the unprivileged submitter)

              $ enroot import -o /tmp/hello.sqsh docker://hello-world # OK
              $ time enroot create --name test-hw /tmp/hello.sqsh
              [ERROR] Timed out waiting for template extraction:
              tank/enroot/data/.templates/<sha>.
              A previous extractor may have crashed; remove tank/enroot/data/.templates/<sha>.tmp
              manually and retry.
              real 10m2.759s

              No .tmp file is left behind. The dataset tank/enroot/data/.templates/<sha> is created but appears empty / unmounted from the user's perspective. ZFS perms include mount, but mount(2) is rejected by the kernel (unrelated to ZFS delegation).

              Hangs the same way under pyxis

              $ srun --gres=gpu:1 --container-image=ubuntu:24.04 cat /etc/os-release
              [2026-04-29T13:28:16] error: Failed to invoke spank plugin stack
              srun: error: spark-4c55: task 0: Exited with exit code 1
              real 10m11.650s

              slurmstepd's enroot create invocation runs as uid 1000 (post-privilege-drop) and hits the same template-extraction timeout.

              Out of scope for this ticket

              • Choice of solution. User namespaces, a small suid helper, an operator-side template-warm daemon, an opt-in flag that pre-mounts as root then chowns — all reasonable; deciding between them is a design question for this fork.
              • Whether to keep the dir backend as the default (it should — see doc/zfs.md's explicit "opt-in" framing).
              • pyxis-side configuration. This is purely about making enroot's ZFS backend usable from an unprivileged caller in the standard way pyxis invokes it.

              Activity

              Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

              Metadata

              Metadata

              Assignees

              No one assigned

                Labels

                No labels
                No labels

                Type

                No type

                Projects

                No projects

                  Milestone

                  No milestone

                  Relationships

                  None yet

                  Development

                  No branches or pull requests

                  Issue actions

                  , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
                  Skip to content

                  ZFS backend: support pyxis (Slurm) workflows where enroot runs after privilege-drop to the job user #9

                  Description

                  @sodre

                  User story

                  As an HPC operator running NVIDIA pyxis on Slurm with the zenroot ZFS backend, I want pyxis-launched containers (srun --container-image=...) to use the ZFS backend's clone-on-create path, so that container startup is near-instant (zfs clone) instead of going through a per-job mksquashfs rebuild.

                  Today the ZFS backend works perfectly when invoked as root, but breaks for the pyxis path because pyxis correctly drops to the user's UID before invoking enroot create, and unprivileged enroot create blocks for ten minutes on template extraction (the documented mount(2) limitation in doc/zfs.md).

                  Problem statement

                  doc/zfs.md flags this:

                  Linux mount(2) limitation: Unprivileged users cannot call mount(2) even with ZFS delegation, so datasets won't auto-mount. Solutions include running enroot create as root or using user namespaces with fs.namespace.unprivileged_userns_clone=1.

                  In the documented Slurm + pyxis topology this isn't a workaround anyone can apply at the calling layer:

                  • slurmstepd starts as root, then drops to the job user (uid 1000 in the repro below) before invoking enroot import / enroot create via the pyxis SPANK plugin.
                  • That is pyxis's correct security posture; container processes run as the submitter, not root.
                  • kernel.unprivileged_userns_clone=1 is already set on the host, but enroot create still hangs ten minutes on Timed out waiting for template extraction.
                  • The result: pyxis fails with error: Failed to invoke spank plugin stack, ten minutes after job launch, on every container job.

                  This is a real-world blocker on a documented integration target (the upstream NVIDIA/enroot README points at pyxis as the canonical Slurm story).

                  What should work

                  After whatever changes are needed, the following should succeed end-to-end on a node where the ZFS backend is configured per doc/zfs.md and pyxis is installed normally:

                  # Submitter is unprivileged (uid 1000); slurmstepd starts root and drops.
                  srun --gres=gpu:1 --container-image=ubuntu:24.04 cat /etc/os-release

                  Acceptance criteria:

                  1. The above completes in seconds on first run (template extracted), and near-instant on subsequent runs (clone-on-create benefit visible to the operator).
                  2. Direct enroot create <foo>.sqsh run as the unprivileged submitter (no sudo) outside Slurm also succeeds.
                  3. The unprivileged path does not require operator-managed wrappers, suid binaries, or AppArmor profile churn beyond what doc/zfs.md prescribes.
                  4. Documentation in doc/zfs.md covers the Slurm + pyxis setup explicitly: any required sysctls, dataset properties, ZFS delegations, and (if applicable) the user-namespace plumbing that makes the unprivileged path work.

                  How the maintainer satisfies these (user-namespace mount delegation, suid enroot-mount helper, an opt-in "pre-mount on the operator side" path, etc.) is out of scope for this issue.

                  Repro environment

                  • 3-node cluster: spark-ctrl (Pi 5, Debian 13) runs slurmctld + slurmdbd + MariaDB; two DGX Sparks (spark-f2ff, spark-4c55, Ubuntu 24.04, GB10 / arm64) run slurmd.
                  • Slurm 25.11.5, built from source; auth/slurm + cred/slurm; SlurmUser=slurm uniform UID 64030.
                  • pyxis v0.23.0, built against the locally-installed Slurm headers; loaded via plugstack.conf:
                    required /usr/local/lib/slurm/spank_pyxis.so runtime_path=/var/lib/pyxis-runtime
                    
                  • enroot packages: enroot_4.1.2.zfs.1-1_arm64.deb and enroot+caps_4.1.2.zfs.1-1_arm64.deb (this fork, latest release).
                  • ZFS layout per doc/zfs.md:
                    tank/enroot/data mountpoint=/var/lib/enroot
                    tank/enroot/data/.templates # auto-created by enroot
                    
                  • ZFS delegation:
                    zfs allow sodre create,mount,clone,destroy,snapshot,rename,hold,release tank/enroot/data
                    zfs allow sodre receive tank/enroot/data/.templates
                    
                  • /etc/enroot/enroot.conf:
                    ENROOT_STORAGE_BACKEND zfs
                    ENROOT_CACHE_PATH /var/cache/enroot
                    ENROOT_DATA_PATH /var/lib/enroot
                    ENROOT_TEMP_PATH /var/tmp/enroot
                    ENROOT_RUNTIME_PATH /run/enroot
                    ENROOT_SQUASH_OPTIONS -comp lzo -noD -processors 20
                    
                  • Sysctl: kernel.unprivileged_userns_clone = 1.
                  • subuid/subgid: sodre:100000:65536.

                  Reproducer

                  Works (as root)

                  $ sudo enroot import -o /tmp/hello.sqsh docker://hello-world # ~0.6 s
                  $ sudo enroot create --name test-hw /tmp/hello.sqsh # ~0.3 s
                  $ sudo enroot create --name test-hw2 /tmp/hello.sqsh # ~0.3 s — clone-on-create
                  $ sudo enroot list
                  test-hw
                  test-hw2
                  $ sudo zfs list -r tank/enroot/data
                  NAME USED AVAIL REFER
                  tank/enroot/data 348K 2.65T 104K
                  tank/enroot/data/.templates 236K 2.65T 128K
                  tank/enroot/data/.templates/812f6e818527af65ea91417ba89e1ffe4a5b73409169fced5c073b636f278fac 108K 2.65T 108K
                  tank/enroot/data/test-hw 8K 2.65T 108K
                  tank/enroot/data/test-hw2 8K 2.65T 108K

                  Hangs (as the unprivileged submitter)

                  $ enroot import -o /tmp/hello.sqsh docker://hello-world # OK
                  $ time enroot create --name test-hw /tmp/hello.sqsh
                  [ERROR] Timed out waiting for template extraction:
                  tank/enroot/data/.templates/<sha>.
                  A previous extractor may have crashed; remove tank/enroot/data/.templates/<sha>.tmp
                  manually and retry.
                  real 10m2.759s

                  No .tmp file is left behind. The dataset tank/enroot/data/.templates/<sha> is created but appears empty / unmounted from the user's perspective. ZFS perms include mount, but mount(2) is rejected by the kernel (unrelated to ZFS delegation).

                  Hangs the same way under pyxis

                  $ srun --gres=gpu:1 --container-image=ubuntu:24.04 cat /etc/os-release
                  [2026-04-29T13:28:16] error: Failed to invoke spank plugin stack
                  srun: error: spark-4c55: task 0: Exited with exit code 1
                  real 10m11.650s

                  slurmstepd's enroot create invocation runs as uid 1000 (post-privilege-drop) and hits the same template-extraction timeout.

                  Out of scope for this ticket

                  • Choice of solution. User namespaces, a small suid helper, an operator-side template-warm daemon, an opt-in flag that pre-mounts as root then chowns — all reasonable; deciding between them is a design question for this fork.
                  • Whether to keep the dir backend as the default (it should — see doc/zfs.md's explicit "opt-in" framing).
                  • pyxis-side configuration. This is purely about making enroot's ZFS backend usable from an unprivileged caller in the standard way pyxis invokes it.

                  Activity

                  Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

                  Metadata

                  Metadata

                  Assignees

                  No one assigned

                    Labels

                    No labels
                    No labels

                    Type

                    No type

                    Projects

                    No projects

                      Milestone

                      No milestone

                      Relationships

                      None yet

                      Development

                      No branches or pull requests

                      Issue actions

                      , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
                      Skip to content

                      ZFS backend: support pyxis (Slurm) workflows where enroot runs after privilege-drop to the job user #9

                      Description

                      @sodre

                      User story

                      As an HPC operator running NVIDIA pyxis on Slurm with the zenroot ZFS backend, I want pyxis-launched containers (srun --container-image=...) to use the ZFS backend's clone-on-create path, so that container startup is near-instant (zfs clone) instead of going through a per-job mksquashfs rebuild.

                      Today the ZFS backend works perfectly when invoked as root, but breaks for the pyxis path because pyxis correctly drops to the user's UID before invoking enroot create, and unprivileged enroot create blocks for ten minutes on template extraction (the documented mount(2) limitation in doc/zfs.md).

                      Problem statement

                      doc/zfs.md flags this:

                      Linux mount(2) limitation: Unprivileged users cannot call mount(2) even with ZFS delegation, so datasets won't auto-mount. Solutions include running enroot create as root or using user namespaces with fs.namespace.unprivileged_userns_clone=1.

                      In the documented Slurm + pyxis topology this isn't a workaround anyone can apply at the calling layer:

                      • slurmstepd starts as root, then drops to the job user (uid 1000 in the repro below) before invoking enroot import / enroot create via the pyxis SPANK plugin.
                      • That is pyxis's correct security posture; container processes run as the submitter, not root.
                      • kernel.unprivileged_userns_clone=1 is already set on the host, but enroot create still hangs ten minutes on Timed out waiting for template extraction.
                      • The result: pyxis fails with error: Failed to invoke spank plugin stack, ten minutes after job launch, on every container job.

                      This is a real-world blocker on a documented integration target (the upstream NVIDIA/enroot README points at pyxis as the canonical Slurm story).

                      What should work

                      After whatever changes are needed, the following should succeed end-to-end on a node where the ZFS backend is configured per doc/zfs.md and pyxis is installed normally:

                      # Submitter is unprivileged (uid 1000); slurmstepd starts root and drops.
                      srun --gres=gpu:1 --container-image=ubuntu:24.04 cat /etc/os-release

                      Acceptance criteria:

                      1. The above completes in seconds on first run (template extracted), and near-instant on subsequent runs (clone-on-create benefit visible to the operator).
                      2. Direct enroot create <foo>.sqsh run as the unprivileged submitter (no sudo) outside Slurm also succeeds.
                      3. The unprivileged path does not require operator-managed wrappers, suid binaries, or AppArmor profile churn beyond what doc/zfs.md prescribes.
                      4. Documentation in doc/zfs.md covers the Slurm + pyxis setup explicitly: any required sysctls, dataset properties, ZFS delegations, and (if applicable) the user-namespace plumbing that makes the unprivileged path work.

                      How the maintainer satisfies these (user-namespace mount delegation, suid enroot-mount helper, an opt-in "pre-mount on the operator side" path, etc.) is out of scope for this issue.

                      Repro environment

                      • 3-node cluster: spark-ctrl (Pi 5, Debian 13) runs slurmctld + slurmdbd + MariaDB; two DGX Sparks (spark-f2ff, spark-4c55, Ubuntu 24.04, GB10 / arm64) run slurmd.
                      • Slurm 25.11.5, built from source; auth/slurm + cred/slurm; SlurmUser=slurm uniform UID 64030.
                      • pyxis v0.23.0, built against the locally-installed Slurm headers; loaded via plugstack.conf:
                        required /usr/local/lib/slurm/spank_pyxis.so runtime_path=/var/lib/pyxis-runtime
                        
                      • enroot packages: enroot_4.1.2.zfs.1-1_arm64.deb and enroot+caps_4.1.2.zfs.1-1_arm64.deb (this fork, latest release).
                      • ZFS layout per doc/zfs.md:
                        tank/enroot/data mountpoint=/var/lib/enroot
                        tank/enroot/data/.templates # auto-created by enroot
                        
                      • ZFS delegation:
                        zfs allow sodre create,mount,clone,destroy,snapshot,rename,hold,release tank/enroot/data
                        zfs allow sodre receive tank/enroot/data/.templates
                        
                      • /etc/enroot/enroot.conf:
                        ENROOT_STORAGE_BACKEND zfs
                        ENROOT_CACHE_PATH /var/cache/enroot
                        ENROOT_DATA_PATH /var/lib/enroot
                        ENROOT_TEMP_PATH /var/tmp/enroot
                        ENROOT_RUNTIME_PATH /run/enroot
                        ENROOT_SQUASH_OPTIONS -comp lzo -noD -processors 20
                        
                      • Sysctl: kernel.unprivileged_userns_clone = 1.
                      • subuid/subgid: sodre:100000:65536.

                      Reproducer

                      Works (as root)

                      $ sudo enroot import -o /tmp/hello.sqsh docker://hello-world # ~0.6 s
                      $ sudo enroot create --name test-hw /tmp/hello.sqsh # ~0.3 s
                      $ sudo enroot create --name test-hw2 /tmp/hello.sqsh # ~0.3 s — clone-on-create
                      $ sudo enroot list
                      test-hw
                      test-hw2
                      $ sudo zfs list -r tank/enroot/data
                      NAME USED AVAIL REFER
                      tank/enroot/data 348K 2.65T 104K
                      tank/enroot/data/.templates 236K 2.65T 128K
                      tank/enroot/data/.templates/812f6e818527af65ea91417ba89e1ffe4a5b73409169fced5c073b636f278fac 108K 2.65T 108K
                      tank/enroot/data/test-hw 8K 2.65T 108K
                      tank/enroot/data/test-hw2 8K 2.65T 108K

                      Hangs (as the unprivileged submitter)

                      $ enroot import -o /tmp/hello.sqsh docker://hello-world # OK
                      $ time enroot create --name test-hw /tmp/hello.sqsh
                      [ERROR] Timed out waiting for template extraction:
                      tank/enroot/data/.templates/<sha>.
                      A previous extractor may have crashed; remove tank/enroot/data/.templates/<sha>.tmp
                      manually and retry.
                      real 10m2.759s

                      No .tmp file is left behind. The dataset tank/enroot/data/.templates/<sha> is created but appears empty / unmounted from the user's perspective. ZFS perms include mount, but mount(2) is rejected by the kernel (unrelated to ZFS delegation).

                      Hangs the same way under pyxis

                      $ srun --gres=gpu:1 --container-image=ubuntu:24.04 cat /etc/os-release
                      [2026-04-29T13:28:16] error: Failed to invoke spank plugin stack
                      srun: error: spark-4c55: task 0: Exited with exit code 1
                      real 10m11.650s

                      slurmstepd's enroot create invocation runs as uid 1000 (post-privilege-drop) and hits the same template-extraction timeout.

                      Out of scope for this ticket

                      • Choice of solution. User namespaces, a small suid helper, an operator-side template-warm daemon, an opt-in flag that pre-mounts as root then chowns — all reasonable; deciding between them is a design question for this fork.
                      • Whether to keep the dir backend as the default (it should — see doc/zfs.md's explicit "opt-in" framing).
                      • pyxis-side configuration. This is purely about making enroot's ZFS backend usable from an unprivileged caller in the standard way pyxis invokes it.

                      Activity

                      Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

                      Metadata

                      Metadata

                      Assignees

                      No one assigned

                        Labels

                        No labels
                        No labels

                        Type

                        No type

                        Projects

                        No projects

                          Milestone

                          No milestone

                          Relationships

                          None yet

                          Development

                          No branches or pull requests

                          Issue actions

                          , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
                          Skip to content

                          ZFS backend: support pyxis (Slurm) workflows where enroot runs after privilege-drop to the job user #9

                          Description

                          @sodre

                          User story

                          As an HPC operator running NVIDIA pyxis on Slurm with the zenroot ZFS backend, I want pyxis-launched containers (srun --container-image=...) to use the ZFS backend's clone-on-create path, so that container startup is near-instant (zfs clone) instead of going through a per-job mksquashfs rebuild.

                          Today the ZFS backend works perfectly when invoked as root, but breaks for the pyxis path because pyxis correctly drops to the user's UID before invoking enroot create, and unprivileged enroot create blocks for ten minutes on template extraction (the documented mount(2) limitation in doc/zfs.md).

                          Problem statement

                          doc/zfs.md flags this:

                          Linux mount(2) limitation: Unprivileged users cannot call mount(2) even with ZFS delegation, so datasets won't auto-mount. Solutions include running enroot create as root or using user namespaces with fs.namespace.unprivileged_userns_clone=1.

                          In the documented Slurm + pyxis topology this isn't a workaround anyone can apply at the calling layer:

                          • slurmstepd starts as root, then drops to the job user (uid 1000 in the repro below) before invoking enroot import / enroot create via the pyxis SPANK plugin.
                          • That is pyxis's correct security posture; container processes run as the submitter, not root.
                          • kernel.unprivileged_userns_clone=1 is already set on the host, but enroot create still hangs ten minutes on Timed out waiting for template extraction.
                          • The result: pyxis fails with error: Failed to invoke spank plugin stack, ten minutes after job launch, on every container job.

                          This is a real-world blocker on a documented integration target (the upstream NVIDIA/enroot README points at pyxis as the canonical Slurm story).

                          What should work

                          After whatever changes are needed, the following should succeed end-to-end on a node where the ZFS backend is configured per doc/zfs.md and pyxis is installed normally:

                          # Submitter is unprivileged (uid 1000); slurmstepd starts root and drops.
                          srun --gres=gpu:1 --container-image=ubuntu:24.04 cat /etc/os-release

                          Acceptance criteria:

                          1. The above completes in seconds on first run (template extracted), and near-instant on subsequent runs (clone-on-create benefit visible to the operator).
                          2. Direct enroot create <foo>.sqsh run as the unprivileged submitter (no sudo) outside Slurm also succeeds.
                          3. The unprivileged path does not require operator-managed wrappers, suid binaries, or AppArmor profile churn beyond what doc/zfs.md prescribes.
                          4. Documentation in doc/zfs.md covers the Slurm + pyxis setup explicitly: any required sysctls, dataset properties, ZFS delegations, and (if applicable) the user-namespace plumbing that makes the unprivileged path work.

                          How the maintainer satisfies these (user-namespace mount delegation, suid enroot-mount helper, an opt-in "pre-mount on the operator side" path, etc.) is out of scope for this issue.

                          Repro environment

                          • 3-node cluster: spark-ctrl (Pi 5, Debian 13) runs slurmctld + slurmdbd + MariaDB; two DGX Sparks (spark-f2ff, spark-4c55, Ubuntu 24.04, GB10 / arm64) run slurmd.
                          • Slurm 25.11.5, built from source; auth/slurm + cred/slurm; SlurmUser=slurm uniform UID 64030.
                          • pyxis v0.23.0, built against the locally-installed Slurm headers; loaded via plugstack.conf:
                            required /usr/local/lib/slurm/spank_pyxis.so runtime_path=/var/lib/pyxis-runtime
                            
                          • enroot packages: enroot_4.1.2.zfs.1-1_arm64.deb and enroot+caps_4.1.2.zfs.1-1_arm64.deb (this fork, latest release).
                          • ZFS layout per doc/zfs.md:
                            tank/enroot/data mountpoint=/var/lib/enroot
                            tank/enroot/data/.templates # auto-created by enroot
                            
                          • ZFS delegation:
                            zfs allow sodre create,mount,clone,destroy,snapshot,rename,hold,release tank/enroot/data
                            zfs allow sodre receive tank/enroot/data/.templates
                            
                          • /etc/enroot/enroot.conf:
                            ENROOT_STORAGE_BACKEND zfs
                            ENROOT_CACHE_PATH /var/cache/enroot
                            ENROOT_DATA_PATH /var/lib/enroot
                            ENROOT_TEMP_PATH /var/tmp/enroot
                            ENROOT_RUNTIME_PATH /run/enroot
                            ENROOT_SQUASH_OPTIONS -comp lzo -noD -processors 20
                            
                          • Sysctl: kernel.unprivileged_userns_clone = 1.
                          • subuid/subgid: sodre:100000:65536.

                          Reproducer

                          Works (as root)

                          $ sudo enroot import -o /tmp/hello.sqsh docker://hello-world # ~0.6 s
                          $ sudo enroot create --name test-hw /tmp/hello.sqsh # ~0.3 s
                          $ sudo enroot create --name test-hw2 /tmp/hello.sqsh # ~0.3 s — clone-on-create
                          $ sudo enroot list
                          test-hw
                          test-hw2
                          $ sudo zfs list -r tank/enroot/data
                          NAME USED AVAIL REFER
                          tank/enroot/data 348K 2.65T 104K
                          tank/enroot/data/.templates 236K 2.65T 128K
                          tank/enroot/data/.templates/812f6e818527af65ea91417ba89e1ffe4a5b73409169fced5c073b636f278fac 108K 2.65T 108K
                          tank/enroot/data/test-hw 8K 2.65T 108K
                          tank/enroot/data/test-hw2 8K 2.65T 108K

                          Hangs (as the unprivileged submitter)

                          $ enroot import -o /tmp/hello.sqsh docker://hello-world # OK
                          $ time enroot create --name test-hw /tmp/hello.sqsh
                          [ERROR] Timed out waiting for template extraction:
                          tank/enroot/data/.templates/<sha>.
                          A previous extractor may have crashed; remove tank/enroot/data/.templates/<sha>.tmp
                          manually and retry.
                          real 10m2.759s

                          No .tmp file is left behind. The dataset tank/enroot/data/.templates/<sha> is created but appears empty / unmounted from the user's perspective. ZFS perms include mount, but mount(2) is rejected by the kernel (unrelated to ZFS delegation).

                          Hangs the same way under pyxis

                          $ srun --gres=gpu:1 --container-image=ubuntu:24.04 cat /etc/os-release
                          [2026-04-29T13:28:16] error: Failed to invoke spank plugin stack
                          srun: error: spark-4c55: task 0: Exited with exit code 1
                          real 10m11.650s

                          slurmstepd's enroot create invocation runs as uid 1000 (post-privilege-drop) and hits the same template-extraction timeout.

                          Out of scope for this ticket

                          • Choice of solution. User namespaces, a small suid helper, an operator-side template-warm daemon, an opt-in flag that pre-mounts as root then chowns — all reasonable; deciding between them is a design question for this fork.
                          • Whether to keep the dir backend as the default (it should — see doc/zfs.md's explicit "opt-in" framing).
                          • pyxis-side configuration. This is purely about making enroot's ZFS backend usable from an unprivileged caller in the standard way pyxis invokes it.

                          Activity

                          Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

                          Metadata

                          Metadata

                          Assignees

                          No one assigned

                            Labels

                            No labels
                            No labels

                            Type

                            No type

                            Projects

                            No projects

                              Milestone

                              No milestone

                              Relationships

                              None yet

                              Development

                              No branches or pull requests

                              Issue actions

                              , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
                              Skip to content

                              ZFS backend: support pyxis (Slurm) workflows where enroot runs after privilege-drop to the job user #9

                              Description

                              @sodre

                              User story

                              As an HPC operator running NVIDIA pyxis on Slurm with the zenroot ZFS backend, I want pyxis-launched containers (srun --container-image=...) to use the ZFS backend's clone-on-create path, so that container startup is near-instant (zfs clone) instead of going through a per-job mksquashfs rebuild.

                              Today the ZFS backend works perfectly when invoked as root, but breaks for the pyxis path because pyxis correctly drops to the user's UID before invoking enroot create, and unprivileged enroot create blocks for ten minutes on template extraction (the documented mount(2) limitation in doc/zfs.md).

                              Problem statement

                              doc/zfs.md flags this:

                              Linux mount(2) limitation: Unprivileged users cannot call mount(2) even with ZFS delegation, so datasets won't auto-mount. Solutions include running enroot create as root or using user namespaces with fs.namespace.unprivileged_userns_clone=1.

                              In the documented Slurm + pyxis topology this isn't a workaround anyone can apply at the calling layer:

                              • slurmstepd starts as root, then drops to the job user (uid 1000 in the repro below) before invoking enroot import / enroot create via the pyxis SPANK plugin.
                              • That is pyxis's correct security posture; container processes run as the submitter, not root.
                              • kernel.unprivileged_userns_clone=1 is already set on the host, but enroot create still hangs ten minutes on Timed out waiting for template extraction.
                              • The result: pyxis fails with error: Failed to invoke spank plugin stack, ten minutes after job launch, on every container job.

                              This is a real-world blocker on a documented integration target (the upstream NVIDIA/enroot README points at pyxis as the canonical Slurm story).

                              What should work

                              After whatever changes are needed, the following should succeed end-to-end on a node where the ZFS backend is configured per doc/zfs.md and pyxis is installed normally:

                              # Submitter is unprivileged (uid 1000); slurmstepd starts root and drops.
                              srun --gres=gpu:1 --container-image=ubuntu:24.04 cat /etc/os-release

                              Acceptance criteria:

                              1. The above completes in seconds on first run (template extracted), and near-instant on subsequent runs (clone-on-create benefit visible to the operator).
                              2. Direct enroot create <foo>.sqsh run as the unprivileged submitter (no sudo) outside Slurm also succeeds.
                              3. The unprivileged path does not require operator-managed wrappers, suid binaries, or AppArmor profile churn beyond what doc/zfs.md prescribes.
                              4. Documentation in doc/zfs.md covers the Slurm + pyxis setup explicitly: any required sysctls, dataset properties, ZFS delegations, and (if applicable) the user-namespace plumbing that makes the unprivileged path work.

                              How the maintainer satisfies these (user-namespace mount delegation, suid enroot-mount helper, an opt-in "pre-mount on the operator side" path, etc.) is out of scope for this issue.

                              Repro environment

                              • 3-node cluster: spark-ctrl (Pi 5, Debian 13) runs slurmctld + slurmdbd + MariaDB; two DGX Sparks (spark-f2ff, spark-4c55, Ubuntu 24.04, GB10 / arm64) run slurmd.
                              • Slurm 25.11.5, built from source; auth/slurm + cred/slurm; SlurmUser=slurm uniform UID 64030.
                              • pyxis v0.23.0, built against the locally-installed Slurm headers; loaded via plugstack.conf:
                                required /usr/local/lib/slurm/spank_pyxis.so runtime_path=/var/lib/pyxis-runtime
                                
                              • enroot packages: enroot_4.1.2.zfs.1-1_arm64.deb and enroot+caps_4.1.2.zfs.1-1_arm64.deb (this fork, latest release).
                              • ZFS layout per doc/zfs.md:
                                tank/enroot/data mountpoint=/var/lib/enroot
                                tank/enroot/data/.templates # auto-created by enroot
                                
                              • ZFS delegation:
                                zfs allow sodre create,mount,clone,destroy,snapshot,rename,hold,release tank/enroot/data
                                zfs allow sodre receive tank/enroot/data/.templates
                                
                              • /etc/enroot/enroot.conf:
                                ENROOT_STORAGE_BACKEND zfs
                                ENROOT_CACHE_PATH /var/cache/enroot
                                ENROOT_DATA_PATH /var/lib/enroot
                                ENROOT_TEMP_PATH /var/tmp/enroot
                                ENROOT_RUNTIME_PATH /run/enroot
                                ENROOT_SQUASH_OPTIONS -comp lzo -noD -processors 20
                                
                              • Sysctl: kernel.unprivileged_userns_clone = 1.
                              • subuid/subgid: sodre:100000:65536.

                              Reproducer

                              Works (as root)

                              $ sudo enroot import -o /tmp/hello.sqsh docker://hello-world # ~0.6 s
                              $ sudo enroot create --name test-hw /tmp/hello.sqsh # ~0.3 s
                              $ sudo enroot create --name test-hw2 /tmp/hello.sqsh # ~0.3 s — clone-on-create
                              $ sudo enroot list
                              test-hw
                              test-hw2
                              $ sudo zfs list -r tank/enroot/data
                              NAME USED AVAIL REFER
                              tank/enroot/data 348K 2.65T 104K
                              tank/enroot/data/.templates 236K 2.65T 128K
                              tank/enroot/data/.templates/812f6e818527af65ea91417ba89e1ffe4a5b73409169fced5c073b636f278fac 108K 2.65T 108K
                              tank/enroot/data/test-hw 8K 2.65T 108K
                              tank/enroot/data/test-hw2 8K 2.65T 108K

                              Hangs (as the unprivileged submitter)

                              $ enroot import -o /tmp/hello.sqsh docker://hello-world # OK
                              $ time enroot create --name test-hw /tmp/hello.sqsh
                              [ERROR] Timed out waiting for template extraction:
                              tank/enroot/data/.templates/<sha>.
                              A previous extractor may have crashed; remove tank/enroot/data/.templates/<sha>.tmp
                              manually and retry.
                              real 10m2.759s

                              No .tmp file is left behind. The dataset tank/enroot/data/.templates/<sha> is created but appears empty / unmounted from the user's perspective. ZFS perms include mount, but mount(2) is rejected by the kernel (unrelated to ZFS delegation).

                              Hangs the same way under pyxis

                              $ srun --gres=gpu:1 --container-image=ubuntu:24.04 cat /etc/os-release
                              [2026-04-29T13:28:16] error: Failed to invoke spank plugin stack
                              srun: error: spark-4c55: task 0: Exited with exit code 1
                              real 10m11.650s

                              slurmstepd's enroot create invocation runs as uid 1000 (post-privilege-drop) and hits the same template-extraction timeout.

                              Out of scope for this ticket

                              • Choice of solution. User namespaces, a small suid helper, an operator-side template-warm daemon, an opt-in flag that pre-mounts as root then chowns — all reasonable; deciding between them is a design question for this fork.
                              • Whether to keep the dir backend as the default (it should — see doc/zfs.md's explicit "opt-in" framing).
                              • pyxis-side configuration. This is purely about making enroot's ZFS backend usable from an unprivileged caller in the standard way pyxis invokes it.

                              Activity

                              Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

                              Metadata

                              Metadata

                              Assignees

                              No one assigned

                                Labels

                                No labels
                                No labels

                                Type

                                No type

                                Projects

                                No projects

                                  Milestone

                                  No milestone

                                  Relationships

                                  None yet

                                  Development

                                  No branches or pull requests

                                  Issue actions