Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 7 additions & 8 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -173,17 +173,16 @@ RUN pip install --no-cache-dir --upgrade pip && \
python -c "import crawl4ai; print('✅ crawl4ai is ready to rock!')" && \
python -c "from playwright.sync_api import sync_playwright; print('✅ Playwright is feeling dramatic!')"

RUN crawl4ai-setup

RUN playwright install --with-deps

RUN mkdir -p /home/appuser/.cache/ms-playwright \
RUN crawl4ai-setup \
&& playwright install --with-deps chromium \
&& crawl4ai-doctor \
&& mkdir -p /home/appuser/.cache/ms-playwright \
&& cp -r /root/.cache/ms-playwright/chromium-* \
/root/.cache/ms-playwright/chromium_headless_shell-* \
/home/appuser/.cache/ms-playwright/ \
&& chown -R appuser:appuser /home/appuser/.cache/ms-playwright

RUN crawl4ai-doctor
&& chown -R appuser:appuser /home/appuser/.cache/ms-playwright \
&& rm -rf /root/.cache/ms-playwright \
&& find "${APP_HOME}" -maxdepth 1 -type f -name '*.core' -delete

# Ensure all cache directories belong to appuser
# This fixes permission issues with .cache/url_seeder and other runtime cache dirs
Expand Down
30 changes: 27 additions & 3 deletions deploy/docker/tests/test_security_container_posture.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,9 @@ def test_no_redis_expose(self, dockerfile):
stripped = line.strip()
if stripped.startswith("#"):
continue
assert not re.match(r"EXPOSE\s+.*\b6379\b", stripped), \
"redis port 6379 must not be EXPOSEd"
assert not re.match(
r"EXPOSE\s+.*\b6379\b", stripped
), "redis port 6379 must not be EXPOSEd"

def test_app_dir_root_owned_readonly(self, dockerfile):
assert "chown -R root:root ${APP_HOME}" in dockerfile
Expand All @@ -66,10 +67,33 @@ def test_playwright_headless_shell_is_copied_to_runtime_cache(self, dockerfile):
dockerfile,
re.DOTALL,
)
assert cache_copy, "Dockerfile must copy Playwright artifacts into appuser's cache"
assert (
cache_copy
), "Dockerfile must copy Playwright artifacts into appuser's cache"
assert "chromium-*" in cache_copy.group("artifacts")
assert "chromium_headless_shell-*" in cache_copy.group("artifacts")

def test_build_artifacts_removed_before_layer_commit(self, dockerfile):
layer = re.search(
r"^RUN crawl4ai-setup(?P<body>(?:(?!^[A-Z]+\s).)*)",
dockerfile,
re.MULTILINE | re.DOTALL,
)
assert layer, "Dockerfile must run crawl4ai-setup"
positions = []
for command in (
"playwright install --with-deps chromium",
"crawl4ai-doctor",
"cp -r /root/.cache/ms-playwright/chromium-*",
"rm -rf /root/.cache/ms-playwright",
"-name '*.core' -delete",
):
assert command in layer.group(
"body"
), f"{command} must run in the crawl4ai-setup layer"
positions.append(layer.group("body").index(command))
assert positions == sorted(positions), "Docker build steps must remain ordered"

def test_runs_as_non_root(self, dockerfile):
assert re.search(r"^USER\s+appuser", dockerfile, re.MULTILINE)

Expand Down
Loading