Skip to content

fix(models): make docker weights volume writable for the HF weight-puller - #853

Merged
benmccown merged 2 commits into
mainfrom
models-e2e-cases
Jul 23, 2026
Merged

fix(models): make docker weights volume writable for the HF weight-puller#853
benmccown merged 2 commits into
mainfrom
models-e2e-cases

Conversation

@benmccown

@benmccown benmccown commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Summary

The deployments-plugin docker backend created the model weights volume as a plain docker named volume, which the daemon creates root-owned (0755). The HF weight-puller runs as its image's default non-root user (e.g. nvs, uid 1000), so hf download --local-dir /model-store failed creating /model-store/.cache with PermissionError [Errno 13], and the deployment errored with Prerequisite '<dep>-puller' failed.

Docker has no fs_group equivalent (that is k8s-only), so the volume stayed unwritable. This blocked all docker vLLM/NIM deployments that pull weights from the files service.

Fix

Docker analogue of a k8s fsGroup, keeping the puller non-root (least privilege):

  • entities.py — add DockerVolumeConfig.init_chmod / init_image.
  • backends/docker/volumes.py + backend.py — in create_volume, when init_chmod is set, run a one-shot busybox container that chmods the freshly created volume so non-root workloads can write it. Idempotent, so re-running on a reconcile is harmless.
  • deployments_plugin/compiler.py — on the docker runtime only, set the weights volume's docker backend config to initChmod=0777 with the configured busybox image. On k8s the volume is unchanged and pod securityContext/fs_group continues to handle it.

busybox is already a first-class dependency of this plugin (used by the existing lora-cache-init container), so this introduces no new concept.

Tests

New unit tests (full deployments-plugin unit suite: 273 passed; models deployments_plugin suite green):

  • docker backend: test_create_volume_runs_init_chmod_container, test_create_volume_without_init_chmod_skips_container
  • compiler: test_docker_weights_volume_requests_init_chmod, test_k8s_weights_volume_has_no_docker_init_chmod

Manual verification (dev pod, docker backend)

  • docker + vLLM (base): busybox chmods the volume → puller runs as its default non-root user (nvs) and exits 0 (all files downloaded) → server READY (http probe 200) → gateway chat-completion works. Previously blocked.
  • docker + NIM (base): same puller path succeeds → NIM selects a vLLM profile, loads weights → READY → gateway chat-completion works.

k8s vLLM/NIM (base + LoRA) were already working and are unaffected (no initChmod emitted on k8s).

Notes

  • docker + LoRA remains a deliberate guardrail (ERROR "LoRA serving is not supported on the docker runtime yet …") — unchanged.
  • Branch name is a leftover from the exploration session; the change itself is the puller/volume fix above.

Summary by CodeRabbit

  • New Features

    • Added optional Docker volume initialization settings to support permission mode and an init helper image (including initChmod / initImage aliases).
    • Docker deployments now initialize permissions for newly created weighted-puller volumes during volume creation.
  • Bug Fixes

    • Ensures permission initialization runs only for newly created volumes and is skipped when the volume already exists.
    • Kubernetes behavior for weighted-puller volume initialization remains unchanged.
  • Tests

    • Added unit tests covering Docker init-container behavior, skip logic, and Docker-vs-Kubernetes compile-time expectations.

@github-actions github-actions Bot added the fix label Jul 22, 2026
@github-actions

github-actions Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor
Suite Lines Covered Line Rate Branch Rate
Unit Tests 27117/34832 77.8% 62.1%
Integration Tests 15921/33544 47.5% 19.9%

The deployments-plugin docker backend created the model weights volume as a
plain docker named volume, which the daemon creates root-owned (0755). The HF
weight-puller runs as its image's default non-root user (e.g. `nvs`, uid 1000),
so `hf download --local-dir /model-store` failed creating /model-store/.cache
with PermissionError and the deployment errored with 'Prerequisite
<dep>-puller failed'. Docker has no fs_group equivalent (that is k8s-only), so
the volume stayed unwritable. This blocked all docker vLLM/NIM deployments that
pull weights from the files service.

Fix (docker analogue of a k8s fsGroup, keeping the puller non-root):
- entities: add DockerVolumeConfig.init_chmod / init_image.
- docker backend create_volume: when init_chmod is set, run a one-shot busybox
  container that chmods the freshly created volume so non-root workloads can
  write it (idempotent, safe to re-run on reconcile).
- models compiler: on the docker runtime only, set the weights volume's docker
  backend config to initChmod=0777 with the configured busybox image. On k8s the
  volume is unchanged and pod securityContext/fs_group continues to handle it.

Verified in a docker-backend dev deployment: the puller runs as its default
non-root user and succeeds, the model reaches READY, and gateway
chat-completion works for both vLLM and NIM engines.

Signed-off-by: Ben McCown <bmccown@nvidia.com>
@benmccown benmccown changed the title fix(models): run docker weight-puller as root so it can write /model-store fix(models): make docker weights volume writable for the HF weight-puller Jul 23, 2026
@benmccown
benmccown marked this pull request as ready for review July 23, 2026 16:05
@benmccown
benmccown requested review from a team as code owners July 23, 2026 16:05
@coderabbitai

coderabbitai Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: a75d9be1-25e1-409d-86eb-4b69f2f1d03d

📥 Commits

Reviewing files that changed from the base of the PR and between e8181b4 and 55b30aa.

📒 Files selected for processing (4)
  • plugins/nemo-deployments/src/nemo_deployments_plugin/backends/docker/volumes.py
  • plugins/nemo-deployments/src/nemo_deployments_plugin/entities.py
  • plugins/nemo-deployments/tests/unit/backends/docker/test_backend_mocked.py
  • services/core/models/tests/unit/controllers/backends/deployments_plugin/test_compiler.py
🚧 Files skipped from review as they are similar to previous changes (3)
  • services/core/models/tests/unit/controllers/backends/deployments_plugin/test_compiler.py
  • plugins/nemo-deployments/src/nemo_deployments_plugin/entities.py
  • plugins/nemo-deployments/src/nemo_deployments_plugin/backends/docker/volumes.py

📝 Walkthrough

Walkthrough

Docker volume configuration now supports optional post-creation permission initialization. Docker weighted-puller compilation emits chmod 0777 settings, while Kubernetes compilation omits Docker initialization.

Changes

Docker volume initialization

Layer / File(s) Summary
Runtime-specific volume configuration
plugins/nemo-deployments/src/nemo_deployments_plugin/entities.py, services/core/models/.../compiler.py, services/core/models/tests/.../test_compiler.py
Docker volume models accept initChmod and initImage; weighted-puller volumes configure them only for Docker runtimes.
Backend configuration wiring
plugins/nemo-deployments/src/nemo_deployments_plugin/backends/docker/backend.py
The Docker backend parses and forwards volume initialization settings.
Volume permission initialization
plugins/nemo-deployments/src/nemo_deployments_plugin/backends/docker/volumes.py, plugins/nemo-deployments/tests/unit/backends/docker/test_backend_mocked.py
Newly created volumes can run a removable helper container that applies chmod; existing volumes and unset settings skip initialization, with tests covering these paths.

Sequence Diagram(s)

sequenceDiagram
  participant DeploymentCompiler
  participant DockerDeploymentBackend
  participant DockerVolumeOperations
  participant DockerEngine
  DeploymentCompiler->>DockerDeploymentBackend: Emit Docker volume initialization settings
  DockerDeploymentBackend->>DockerVolumeOperations: Forward init_chmod and init_image
  DockerVolumeOperations->>DockerEngine: Create or reuse named volume
  DockerVolumeOperations->>DockerEngine: Run helper container with chmod on /vol
Loading

Possibly related PRs

Suggested labels: test

Suggested reviewers: tylersbray

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: making Docker weights volumes writable for the HF weight-puller.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch models-e2e-cases

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
services/core/models/tests/unit/controllers/backends/deployments_plugin/test_compiler.py (1)

42-51: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Assert the exact helper image.

assert docker_cfg.init_image would pass for an incorrect image. Assert the configured BusyBox image value to protect this cross-layer contract.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@services/core/models/tests/unit/controllers/backends/deployments_plugin/test_compiler.py`
around lines 42 - 51, Update test_docker_weights_volume_requests_init_chmod to
assert docker_cfg.init_image equals the configured BusyBox image value, rather
than only checking that it is truthy; preserve the existing init_chmod
assertion.
plugins/nemo-deployments/tests/unit/backends/docker/test_backend_mocked.py (1)

97-103: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Update this assertion with the shell-free fix.

The test currently locks in sh -c. After hardening production code, assert entrypoint == ["chmod"] and command == ["0777", "/vol"].

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@plugins/nemo-deployments/tests/unit/backends/docker/test_backend_mocked.py`
around lines 97 - 103, Update the assertions for
mock_docker_client.containers.run in the relevant test to verify the shell-free
invocation: assert entrypoint equals ["chmod"] and command equals ["0777",
"/vol"]. Remove the existing command assertion that expects ["chmod 0777 /vol"],
while preserving the remove and volume-mount assertions.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@plugins/nemo-deployments/src/nemo_deployments_plugin/backends/docker/volumes.py`:
- Around line 48-53: Remove shell evaluation from volume initialization in
volumes.py around client.containers.run: validate init_chmod as an allowed chmod
mode, invoke chmod directly instead of through sh -c, and pass the mode and /vol
as separate arguments. Update test_backend_mocked.py to assert the direct chmod
entrypoint and argument list.

---

Nitpick comments:
In `@plugins/nemo-deployments/tests/unit/backends/docker/test_backend_mocked.py`:
- Around line 97-103: Update the assertions for
mock_docker_client.containers.run in the relevant test to verify the shell-free
invocation: assert entrypoint equals ["chmod"] and command equals ["0777",
"/vol"]. Remove the existing command assertion that expects ["chmod 0777 /vol"],
while preserving the remove and volume-mount assertions.

In
`@services/core/models/tests/unit/controllers/backends/deployments_plugin/test_compiler.py`:
- Around line 42-51: Update test_docker_weights_volume_requests_init_chmod to
assert docker_cfg.init_image equals the configured BusyBox image value, rather
than only checking that it is truthy; preserve the existing init_chmod
assertion.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: e74901a1-0714-4eff-900e-2b1af018eebb

📥 Commits

Reviewing files that changed from the base of the PR and between 53947a1 and e8181b4.

📒 Files selected for processing (6)
  • plugins/nemo-deployments/src/nemo_deployments_plugin/backends/docker/backend.py
  • plugins/nemo-deployments/src/nemo_deployments_plugin/backends/docker/volumes.py
  • plugins/nemo-deployments/src/nemo_deployments_plugin/entities.py
  • plugins/nemo-deployments/tests/unit/backends/docker/test_backend_mocked.py
  • services/core/models/src/nmp/core/models/controllers/backends/deployments_plugin/compiler.py
  • services/core/models/tests/unit/controllers/backends/deployments_plugin/test_compiler.py

@benmccown benmccown self-assigned this Jul 23, 2026
Comment thread plugins/nemo-deployments/src/nemo_deployments_plugin/entities.py Outdated
Comment thread plugins/nemo-deployments/src/nemo_deployments_plugin/entities.py Outdated
@benmccown
benmccown added this pull request to the merge queue Jul 23, 2026
@benmccown
benmccown removed this pull request from the merge queue due to a manual request Jul 23, 2026

@tylersbray tylersbray left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 approving, see DM for loose ends. Thanks!

- Invoke chmod directly (entrypoint=["chmod"], command=[mode, "/vol"]) instead
  of via `sh -c`, so a mode value is never shell-interpolated (defense in depth
  even though the only caller passes a hardcoded '0777').
- Only run the init-chmod container on a fresh volume create, not on reuse, so
  reconciles don't spin up a helper container for an already-initialized volume.
- Add a comment explaining why chmod (uid-agnostic) is used over chown.
- Shorten the DockerVolumeConfig field descriptions and drop 'one-shot' wording.
- Tests: assert the shell-free chmod invocation, the exact busybox init image,
  and that init-chmod is skipped when the volume already exists.

Signed-off-by: Ben McCown <bmccown@nvidia.com>
@benmccown
benmccown added this pull request to the merge queue Jul 23, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Jul 23, 2026
@benmccown
benmccown added this pull request to the merge queue Jul 23, 2026
Merged via the queue into main with commit dbb8280 Jul 23, 2026
60 checks passed
@benmccown
benmccown deleted the models-e2e-cases branch July 23, 2026 18:39
AnuradhaKaruppiah pushed a commit to AnuradhaKaruppiah/nemo-platform that referenced this pull request Jul 24, 2026
…ller (NVIDIA-NeMo#853)

* fix(models): make docker weights volume writable for the HF puller

The deployments-plugin docker backend created the model weights volume as a
plain docker named volume, which the daemon creates root-owned (0755). The HF
weight-puller runs as its image's default non-root user (e.g. `nvs`, uid 1000),
so `hf download --local-dir /model-store` failed creating /model-store/.cache
with PermissionError and the deployment errored with 'Prerequisite
<dep>-puller failed'. Docker has no fs_group equivalent (that is k8s-only), so
the volume stayed unwritable. This blocked all docker vLLM/NIM deployments that
pull weights from the files service.

Fix (docker analogue of a k8s fsGroup, keeping the puller non-root):
- entities: add DockerVolumeConfig.init_chmod / init_image.
- docker backend create_volume: when init_chmod is set, run a one-shot busybox
  container that chmods the freshly created volume so non-root workloads can
  write it (idempotent, safe to re-run on reconcile).
- models compiler: on the docker runtime only, set the weights volume's docker
  backend config to initChmod=0777 with the configured busybox image. On k8s the
  volume is unchanged and pod securityContext/fs_group continues to handle it.

Verified in a docker-backend dev deployment: the puller runs as its default
non-root user and succeeds, the model reaches READY, and gateway
chat-completion works for both vLLM and NIM engines.

Signed-off-by: Ben McCown <bmccown@nvidia.com>

* refactor(models): address PR review on docker volume init-chmod

- Invoke chmod directly (entrypoint=["chmod"], command=[mode, "/vol"]) instead
  of via `sh -c`, so a mode value is never shell-interpolated (defense in depth
  even though the only caller passes a hardcoded '0777').
- Only run the init-chmod container on a fresh volume create, not on reuse, so
  reconciles don't spin up a helper container for an already-initialized volume.
- Add a comment explaining why chmod (uid-agnostic) is used over chown.
- Shorten the DockerVolumeConfig field descriptions and drop 'one-shot' wording.
- Tests: assert the shell-free chmod invocation, the exact busybox init image,
  and that init-chmod is skipped when the volume already exists.

Signed-off-by: Ben McCown <bmccown@nvidia.com>

---------

Signed-off-by: Ben McCown <bmccown@nvidia.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants