Skip to content

Conversation

@ItzNotABug
Copy link
Member

@ItzNotABug ItzNotABug commented Sep 8, 2025

What does this PR do?

Screenshot 2025-11-09 at 11 44 30 AM

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

    • Added a Docker Compose service for easy local run at http://localhost:3000.
    • /v1/screenshots now accepts optional headers (defaults to empty), reducing required input.
  • Refactor

    • Container runtime simplified and hardened; app runs as a non-root user for improved security and reliable browser automation.
  • Chores

    • Streamlined Docker build for smaller images and faster builds.
    • Updated package manager and swapped the full browser package for a lighter automation package.

@ItzNotABug ItzNotABug self-assigned this Sep 8, 2025
@coderabbitai
Copy link

coderabbitai bot commented Sep 8, 2025

Walkthrough

Replaces 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 appwrite-browser (build ., image appwrite/browser:local, ports 3000:3000, PORT=3000). Updates package.json packageManager and replaces playwright with playwright-core (^1.52.0). src/index.js imports playwright-core and uses PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH for executablePath.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Inspect Dockerfile: multi-stage correctness, pnpm installation and frozen install, cache/prune steps, Chromium and font packages, removal of Chromium artifacts, user/group creation, file copy strategy, environment variables, ENTRYPOINT/CMD and permissions.
  • Validate Playwright runtime changes: replacing playwright with playwright-core, usage of PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD, and that the executable path points to an available Chromium binary in the final image.
  • Check package.json: packageManager format and the dependency swap (playwrightplaywright-core) for transitive impacts.
  • Review src/index.js: import change to playwright-core, executablePath wiring, and any type/annotation updates or potential runtime failure modes if env var is unset.
  • Verify docker-compose: service build context, Dockerfile path, image tag, port mapping, and environment variable.

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Optimize: size' directly aligns with the PR's main objective of reducing the Docker image size from ~1.04-1.05 GB to ~888 MB through multi-stage builds, dependency optimization, and runtime cleanup.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch smaller-size

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.

❤️ Share

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

Copy link

@coderabbitai coderabbitai bot left a 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 domain

Normalize 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 screenshots

Helps 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 Chromium

Add 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-node is 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 exposure

Not 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 yamllint

Minor 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

📥 Commits

Reviewing files that changed from the base of the PR and between f9a2c48 and 094b6c3.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is 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 reference playwright-core or 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 path

Add 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_PATH points to an installed Chromium binary in your container.

@ItzNotABug ItzNotABug requested a review from Meldiron November 9, 2025 06:16
Copy link

@coderabbitai coderabbitai bot left a 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 the packageManager field in package.json. Verify that package.json declares packageManager: [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-zenhei from 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 add calls. 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

📥 Commits

Reviewing files that changed from the base of the PR and between 094b6c3 and e1880c6.

📒 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 chrome user is created before file operations, and tini is 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 the default:safe pattern 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:safe pattern 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.

Copy link

@coderabbitai coderabbitai bot left a 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

📥 Commits

Reviewing files that changed from the base of the PR and between e1880c6 and 1be05ed.

📒 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

Copy link

@coderabbitai coderabbitai bot left a 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

📥 Commits

Reviewing files that changed from the base of the PR and between 1be05ed and 4fea438.

📒 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 playwright with playwright-core is the right choice for this Docker image optimization. playwright-core omits 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).

Copy link

@coderabbitai coderabbitai bot left a 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-zenhei from the edge community repository introduces instability risk; edge packages can change unexpectedly. This could break reproducibility in future builds.

Recommended options:

  1. Pin an explicit version if the edge package supports it: --repository=... font-wqy-zenhei@VERSION
  2. Check if a compatible font is available in the stable repository
  3. Accept the edge dependency if reproducibility is not critical

35-40: Use COPY --chown to 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

📥 Commits

Reviewing files that changed from the base of the PR and between 4fea438 and fb7c215.

📒 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-lockfile is correct for reproducible production builds. Cache cleanup at line 10 is properly executed as root before any USER directive.

Ensure package.json's packageManager field 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.

@ItzNotABug ItzNotABug merged commit e2a3f38 into main Nov 9, 2025
3 checks passed
@ItzNotABug ItzNotABug deleted the smaller-size branch November 9, 2025 09:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants