-
Notifications
You must be signed in to change notification settings - Fork 2
Optimize: size #6
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
WalkthroughReplaces the Docker build with an Alpine-based multi-stage flow (base: node:22.13.1-alpine3.20), installs pnpm globally ([email protected]), uses pnpm to install production deps with a frozen lockfile and aggressively prunes caches, and produces a final image that installs Chromium, fonts and tini, creates a non-root chrome user, sets PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD and PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH, and uses ENTRYPOINT ["tini","--"] with CMD ["node","src/index.js"]. Adds a docker-compose service Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes
Pre-merge checks and finishing touches✅ Passed checks (3 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/index.js (1)
101-129: Lighthouse response handling breaks when json=false
JSON.parse(results.report)will throw for HTML/CSV reports.-const results = await playAudit({ +const results = await playAudit({ /* ... */ -}); +}); - await context.close(); - return JSON.parse(results.report); + await context.close(); + if (body.json) { + event.node.res.setHeader("Content-Type", "application/json"); + return JSON.parse(results.report); + } + if (body.html) { + event.node.res.setHeader("Content-Type", "text/html; charset=utf-8"); + return results.report; + } + if (body.csv) { + event.node.res.setHeader("Content-Type", "text/csv; charset=utf-8"); + return results.report; + } + return { ok: true };
🧹 Nitpick comments (9)
src/index.js (5)
29-31: JSDoc type import points to 'playwright', not 'playwright-core'Keep types consistent to avoid editor/IDE confusion.
-/** @type {Partial<import('playwright').BrowserContext>} defaultContext */ +/** @type {Partial<import('playwright-core').BrowserContext>} defaultContext */
57-70: Sanitize/normalize override headers for the appwrite domainNormalize keys and prevent overriding sensitive hop-by-hop headers.
- return await route.continue({ - headers: { - ...request.headers(), - ...body.headers, - }, - }); + const base = request.headers(); + const safe = Object.fromEntries( + Object.entries(body.headers).map(([k, v]) => [k.toLowerCase(), v]), + ); + // Disallow overriding dangerous headers + for (const h of ["host", "content-length", "connection", "transfer-encoding"]) { + delete safe[h]; + } + return await route.continue({ headers: { ...base, ...safe } });
80-86: Return correct content-type for screenshotsHelps clients treat the response as PNG.
-const screen = await page.screenshot(); +const screen = await page.screenshot(); @@ -await context.close(); -return screen; +await context.close(); +event.node.res.setHeader("Content-Type", "image/png"); +return screen;
131-138: Graceful shutdown to avoid orphaned ChromiumAdd signal handlers to close the shared browser on exit.
router.get( @@ ); createServer(toNodeListener(app)).listen(port); console.log(`Server running on port http://0.0.0.0:${port}`); + +for (const sig of ["SIGTERM", "SIGINT"]) { + process.on(sig, async () => { + try { await browser.close(); } catch {} + process.exit(0); + }); +}
37-42: SSRF hardening (optional, but recommended)The service fetches arbitrary URLs. Consider an allowlist (e.g., only appwrite network) or DNS/IP blocking (RFC1918).
I can propose a small URL allowlist+CIDR block check if desired.
Also applies to: 57-70, 72-79
Dockerfile (2)
27-33: Pin base image for reproducibility
zenika/alpine-chrome:with-nodeis moving. Pin a specific tag or digest to avoid surprises.Example:
-FROM zenika/alpine-chrome:with-node +FROM zenika/alpine-chrome:with-node-22@sha256:<digest>
42-43: Document port exposureNot required, but helpful for users and tooling.
+# document the listening port +EXPOSE 3000 # start the application CMD ["node", "src/index.js"]docker-compose.yml (2)
1-10: Add newline at EOF to satisfy yamllintMinor formatting fix.
services: appwrite-browser: build: context: . dockerfile: Dockerfile image: appwrite/browser:local ports: - "3000:3000" environment: - PORT=3000 +
2-10: Optional: improve stability and operability
- Increase shared memory for Chromium; or keep
--disable-dev-shm-usage.- Add a healthcheck.
services: appwrite-browser: @@ ports: - "3000:3000" environment: - PORT=3000 + shm_size: "512m" + healthcheck: + test: ["CMD", "wget", "-qO-", "http://localhost:3000/v1/health"] + interval: 10s + timeout: 5s + retries: 3
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (4)
Dockerfile(1 hunks)docker-compose.yml(1 hunks)package.json(1 hunks)src/index.js(3 hunks)
🧰 Additional context used
🪛 YAMLlint (1.37.1)
docker-compose.yml
[error] 10-10: no new line character at the end of file
(new-line-at-end-of-file)
🔇 Additional comments (3)
package.json (1)
21-21: Remove lingering “playwright” import and verify Lighthouse integration
- src/index.js (around line 29): update the JSDoc
import('playwright')to referenceplaywright-coreor remove it entirely.- Confirm that playwright-lighthouse ^4.0.0 peer-dependencies are satisfied by playwright-core ^1.52.0.
src/index.js (2)
44-87: Concurrency on a single debug port (9222)Concurrent /v1/reports calls may contend on the shared CDP port. Consider a queue or dedicated Chromium for LH runs.
Would you like a lightweight in-process queue (p-limit) around playAudit calls?
Also applies to: 101-129
11-12: System Chrome via playwright-core: add container stability flags and pin executable pathAdd the following flags to improve Chromium stability in containers and explicitly set the binary path:
-import { chromium } from "playwright-core"; +import { chromium } from "playwright-core"; @@ -const browser = await chromium.launch({ - args: ["--remote-debugging-port=9222"], - executablePath: process.env.PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH, -}); +const browser = await chromium.launch({ + args: [ + "--remote-debugging-port=9222", + "--disable-dev-shm-usage", + "--no-first-run", + "--no-default-browser-check", + ], + executablePath: process.env.PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH, +});Ensure that
PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATHpoints to an installed Chromium binary in your container.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
♻️ Duplicate comments (1)
Dockerfile (1)
7-7: Verify pnpm version consistency with package.json.Line 7 installs
[email protected]globally. Past reviews flagged a mismatch between the Dockerfile's pnpm version and thepackageManagerfield inpackage.json. Verify thatpackage.jsondeclarespackageManager: [email protected](or the appropriate version) to ensure lockfile semantics remain consistent.To confirm, run:
#!/bin/bash # Check package.json for packageManager field grep -A1 '"packageManager"' package.json
🧹 Nitpick comments (2)
Dockerfile (2)
23-23: Use stable repository for font, not Alpine edge.Line 23 pulls
font-wqy-zenheifrom the edge community repository, which is unstable and may change without notice. Consider using only stable repositories or documenting why edge is required.- apk add --no-cache font-wqy-zenhei --repository=http://dl-cdn.alpinelinux.org/alpine/edge/community && \ + apk add --no-cache font-wqy-zenhei --repository=http://dl-cdn.alpinelinux.org/alpine/v3.20/community && \
18-23: Consolidate apk add invocations for efficiency.Lines 18–22 and line 23 are separate
apk addcalls. Combining them into a single command reduces layer overhead and improves caching:RUN apk upgrade --no-cache --available && \ apk add --no-cache \ chromium \ ttf-freefont \ font-noto-emoji \ + font-wqy-zenhei \ - tini && \ - apk add --no-cache font-wqy-zenhei --repository=http://dl-cdn.alpinelinux.org/alpine/edge/community && \ + tini \ + --repository=http://dl-cdn.alpinelinux.org/alpine/v3.20/community && \ # remove unnecessary chromium files to save space(This also aligns with the repository fix suggested above.)
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
Dockerfile(1 hunks)
🔇 Additional comments (4)
Dockerfile (4)
25-30: Confirm chrome_sandbox removal does not disable needed security features.Line 30 removes
chrome_sandbox, which may be required for Chromium's sandboxing mode if your use case requires it. Verify that removing this does not degrade security or break Playwright automation workflows that rely on the sandbox.If sandboxing is not needed, consider adding a comment explaining the trade-off (size vs. security/features).
1-1: Confirm Alpine 3.20 version choice.Both stages pin
alpine3.20. Verify this is either the latest stable Alpine version or a deliberate pin to ensure reproducibility and avoid unintended upgrades.Also applies to: 15-15
34-46: Build, permissions, and signal handling look correct.Environment variables for Playwright integration are properly set, the non-root
chromeuser is created before file operations, andtiniis correctly configured as ENTRYPOINT to handle container signals. The multi-stage approach successfully reduces image size by leaving build artifacts in the base stage.
12-12: I need to gather more specific information about what thedefault:safepattern removes and playwright-core's requirements.Let me search more specifically for what modclean's default:safe pattern actually removes:
Based on my verification, I can now provide the rewritten review comment:
The
default:safepattern should preserve playwright-core dependencies.The modclean "safe" pattern removes documentation and configuration files like readme*, license files, .gitignore, makefile*, example*, and changelog*—not browser binaries or cache directories. Playwright typically stores browser binaries in node_modules/playwright-core/.local-browsers and these can be cached with the package code. Since the pattern does not target hidden directories or binaries, it should not break playwright-core functionality.
However, modclean documentation acknowledges that "there could be one-off cases where a module could be affected", so testing the Docker build or verifying runtime behavior remains prudent for production confidence.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
package.json(1 hunks)src/index.js(3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- src/index.js
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
package.json(1 hunks)
🔇 Additional comments (2)
package.json (2)
17-17: ✓ packageManager format is now correct.The space after
@has been removed. The format"[email protected]"conforms to the standard packageManager specification.
21-21: ✓ Dependency swap aligns with size optimization objectives.Replacing
playwrightwithplaywright-coreis the right choice for this Docker image optimization.playwright-coreomits bundled browser binaries, which reduces the package footprint when Chromium is provided via the Alpine-based system packages (as indicated in the Dockerfile changes mentioned in the PR summary).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (2)
Dockerfile (2)
20-20: Consider alternatives to edge repository.Pulling
font-wqy-zenheifrom the edge community repository introduces instability risk; edge packages can change unexpectedly. This could break reproducibility in future builds.Recommended options:
- Pin an explicit version if the edge package supports it:
--repository=... font-wqy-zenhei@VERSION- Check if a compatible font is available in the stable repository
- Accept the edge dependency if reproducibility is not critical
35-40: UseCOPY --chownto explicitly set file ownership.COPY by default places files owned by root in the image; Docker provides COPY --chown to set ownership at copy time (preferred). USER sets the effective user for following RUN/CMD/ENTRYPOINT, but should not be relied upon to set ownership of files copied with COPY.
The current code will function correctly—files will be root-owned and readable by the chrome user. However, for clarity and best practice, update lines 38–40 to explicitly set ownership:
COPY --chown=chrome:chrome package.json ./ COPY --chown=chrome:chrome --from=base /app/node_modules ./node_modules COPY --chown=chrome:chrome src/ ./src/This removes ambiguity and ensures the chrome user owns these files if the application needs to modify them at runtime.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
Dockerfile(1 hunks)src/index.js(3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- src/index.js
🔇 Additional comments (3)
Dockerfile (3)
1-11: Base stage setup looks solid with proper version pinning.Using Alpine with specific versions (22.13.1-alpine3.20, [email protected]) and
--frozen-lockfileis correct for reproducible production builds. Cache cleanup at line 10 is properly executed as root before any USER directive.Ensure package.json's
packageManagerfield is updated to match[email protected].
1-40: Well-executed multi-stage Alpine build with thoughtful optimizations.The approach demonstrates solid Docker practices: Alpine base image, aggressive cleanup of build artifacts and unnecessary system files, security-conscious non-root user setup, and tini for proper signal handling. The 15% size reduction is meaningful and well-targeted.
Ensure the verification concerns above (chromium path, edge repository, USER/COPY ordering) are resolved before merge.
32-32: ****The chromium executable path is correct. Alpine's chromium package provides
/usr/bin/chromium-browser, which is the appropriate path for Playwright configuration on Alpine Linux.
What does this PR do?
Should be around 1.04/1.05GB now.Should be around 888MB now.
Test Plan
Manual.
Related PRs and Issues
N/A.
Have you read the Contributing Guidelines on issues?
Yes.
Summary by CodeRabbit
New Features
Refactor
Chores