fix(docker): repair app cache parent ownership (#6158)

* fix(docker): repair app cache parent ownership

* fix(docker): avoid walking mounted model cache

* test(docker): exercise nested cache ownership

---------

Co-authored-by: Raul <9117159+raultcj@users.noreply.github.com>
This commit is contained in:
rauljua
2026-09-05 18:05:38 +02:00
committed by GitHub
co-authored by Raul
parent affaee1e66
commit c7a8637475
2 changed files with 94 additions and 1 deletions
+10 -1
View File
@@ -96,7 +96,16 @@ repair_bind_mount_ownership() {
# Repair image-owned writable paths without walking into bind-mounted host
# trees, then repair the app-owned mount roots separately.
repair_app_tree_ownership
for dir in /app/data /app/logs /app/.ssh /app/.cache/huggingface /app/.local; do
# Docker creates the parent of the HuggingFace bind mount as root before this
# entrypoint runs. Repair only the parent directory itself so app-user caches
# such as /app/.cache/vllm and /app/.cache/flashinfer can be created without
# recursively walking the mounted model cache.
chown "$PUID:$PGID" /app/.cache 2>/dev/null || true
# The Hugging Face cache can contain hundreds of gigabytes and is a nested
# mount with its own ownership contract. Repair its mount root so new cache
# entries are writable, but never traverse or rewrite existing model files.
chown "$PUID:$PGID" /app/.cache/huggingface 2>/dev/null || true
for dir in /app/data /app/logs /app/.ssh /app/.local; do
repair_bind_mount_ownership "$dir"
done
+84
View File
@@ -1,9 +1,14 @@
"""Static regressions for Docker/devops hardening contracts."""
import ast
import os
import re
import shutil
import subprocess
import uuid
from pathlib import Path
import pytest
import yaml
from starlette.applications import Starlette
from starlette.middleware.cors import CORSMiddleware
@@ -115,6 +120,85 @@ def test_docker_entrypoint_ownership_repair_stays_inside_expected_mounts():
assert "Skipping recursive ownership repair" in script
def test_docker_entrypoint_repairs_cache_parent_without_recursive_walk():
"""Pin the hard-coded container-path contract without running entrypoint as root."""
script = (ROOT / "docker" / "entrypoint.sh").read_text(encoding="utf-8")
app_repair = script.index("repair_app_tree_ownership\n")
cache_parent_repair = script.index(
'chown "$PUID:$PGID" /app/.cache 2>/dev/null || true'
)
mounted_cache_root_repair = script.index(
'chown "$PUID:$PGID" /app/.cache/huggingface 2>/dev/null || true'
)
assert app_repair < cache_parent_repair < mounted_cache_root_repair
assert 'repair_tree_ownership "/app/.cache"' not in script
assert 'repair_bind_mount_ownership "/app/.cache/huggingface"' not in script
@pytest.mark.skipif(shutil.which("docker") is None, reason="Docker CLI is unavailable")
def test_docker_entrypoint_cache_parent_with_nested_volume():
"""Run the real entrypoint against a disposable nested-volume layout."""
image = os.environ.get("ODYSSEUS_DOCKER_TEST_IMAGE", "odysseus-odysseus:latest")
if subprocess.run(
["docker", "image", "inspect", image],
capture_output=True,
text=True,
check=False,
).returncode != 0:
pytest.skip(f"Docker test image is unavailable: {image}")
volume = f"odysseus-cache-parent-test-{uuid.uuid4().hex}"
subprocess.run(
["docker", "volume", "create", volume],
capture_output=True,
text=True,
check=True,
)
try:
subprocess.run(
[
"docker", "run", "--rm", "--pull=never",
"--entrypoint", "sh",
"-v", f"{volume}:/fixture",
image,
"-c", "mkdir -p /fixture/nested && touch /fixture/nested/sentinel",
],
capture_output=True,
text=True,
check=True,
)
result = subprocess.run(
[
"docker", "run", "--rm", "--pull=never",
"-e", "PUID=23456",
"-e", "PGID=23456",
"-v", f"{volume}:/app/.cache/huggingface",
image,
"sh", "-c",
"mkdir -p /app/.cache/vllm && "
"touch /app/.cache/vllm/probe && "
"printf 'CACHE_TEST %s %s %s %s\\n' "
"\"$(stat -c %u /app/.cache)\" "
"\"$(stat -c %u /app/.cache/vllm/probe)\" "
"\"$(stat -c %u /app/.cache/huggingface)\" "
"\"$(stat -c %u /app/.cache/huggingface/nested/sentinel)\"",
],
capture_output=True,
text=True,
check=True,
)
finally:
subprocess.run(
["docker", "volume", "rm", "-f", volume],
capture_output=True,
text=True,
check=False,
)
assert "CACHE_TEST 23456 23456 23456 0" in result.stdout
def test_dockerignore_excludes_secrets_editor_backups():
patterns = set((ROOT / ".dockerignore").read_text(encoding="utf-8").splitlines())
assert {