node.jsdockerdockerfileplaywrightadonisjs-ace

Playwright browser launch error in Docker container with AdonisJS project


I'm encountering an issue with my Dockerized AdonisJS project where Playwright is unable to launch the browser, resulting in the error message:

M browserType.launch: Executable doesn't exist at /root/.cache/ms-playwright/chromium-1105/chrome-linux/chrome

error

Here's my Dockerfile:

FROM node:20.11.1-alpine as base

# Install dependencies needed by Playwright
RUN apk add --no-cache \
    libxkbcommon \
    dbus \
    ttf-freefont \
    udev \
    xvfb \
    zlib \
    chromium \
    chromium-chromedriver \
    nss \
    freetype \
    freetype-dev \
    harfbuzz \
    ca-certificates \
    && rm -rf /var/cache/apk/*

# install dependences
FROM base as deps
WORKDIR /app
COPY . .
COPY package.json package-lock.json ./
RUN npm install
# Install Playwright and browsers
RUN npx playwright install

# Production only deps stage
FROM base as production-deps
WORKDIR /app
ADD package.json package-lock.json ./
RUN npm install 
# Install Playwright and browsers
RUN npx playwright install

# Build
FROM base as build
WORKDIR /app
COPY --from=deps /app/node_modules /app/node_modules
COPY . .
COPY .env.example .env
RUN npm run build

# Set the entry point for the container
FROM base
ENV NODE_ENV=production
WORKDIR /app
COPY --from=production-deps /app/node_modules /app/node_modules
COPY --from=build /app/build /app
COPY .env.example .env
EXPOSE 3333
CMD ["node", "./bin/server.js"]

The Dockerfile installs all the necessary dependencies for Playwright and my AdonisJS project. However, when I run the container, I receive the mentioned error.

I've verified that npx playwright install is executed during the Docker build process. Additionally, the Dockerfile copies all files, including the modules, into a folder named app.

I'm trying that but doesnt work Playwright won't launch browser in Docker container

Package JSON

{
  "name": "",
  "version": "0.0.0",
  "private": true,
  "type": "module",
  "license": "UNLICENSED", 
  "scripts": {
    "start": "node bin/server.js",
    "build": "node ace build",
    "dev": "node ace serve --watch",
    "test": "node ace test",
    "lint": "eslint .",
    "format": "prettier --write .",
    "typecheck": "tsc --noEmit"
  },
  "imports": {
    "#controllers/*": "./app/controllers/*.js",
    "#exceptions/*": "./app/exceptions/*.js",
    "#models/*": "./app/models/*.js",
    "#mails/*": "./app/mails/*.js",
    "#services/*": "./app/services/*.js",
    "#listeners/*": "./app/listeners/*.js",
    "#events/*": "./app/events/*.js",
    "#middleware/*": "./app/middleware/*.js",
    "#validators/*": "./app/validators/*.js",
    "#providers/*": "./providers/*.js",
    "#policies/*": "./app/policies/*.js",
    "#abilities/*": "./app/abilities/*.js",
    "#database/*": "./database/*.js",
    "#start/*": "./start/*.js",
    "#tests/*": "./tests/*.js",
    "#config/*": "./config/*.js"
  },
  "devDependencies": {
    "@adonisjs/assembler": "^7.1.1",
    "@adonisjs/eslint-config": "^1.2.1",
    "@adonisjs/prettier-config": "^1.2.1",
    "@adonisjs/tsconfig": "^1.2.1",
    "@japa/api-client": "^2.0.2",
    "@japa/assert": "^2.1.0",
    "@japa/plugin-adonisjs": "^3.0.0",
    "@japa/runner": "^3.1.1",
    "@swc/core": "^1.3.107",
    "@types/hbs": "^4.0.4",
    "@types/jsdom": "^21.1.6",
    "@types/luxon": "^3.4.2",
    "@types/node": "^20.11.25",
    "autoprefixer": "^10.4.17",
    "eslint": "^8.56.0",
    "pino-pretty": "^10.3.1",
    "postcss": "^8.4.35",
    "tailwindcss": "^3.4.1",
    "ts-node": "^10.9.2",
    "typescript": "^5.3.3",
    "vite": "^5.1.4"
  },
  "dependencies": {
    "@adonisjs/auth": "^9.1.1",
    "@adonisjs/core": "^6.2.2",
    "@adonisjs/cors": "^2.2.1",
    "@adonisjs/lucid": "^20.1.0",
    "@adonisjs/session": "^7.1.1",
    "@adonisjs/vite": "^2.0.2",
    "@vinejs/vine": "^1.7.1",
    "better-sqlite3": "^9.4.3",
    "edge.js": "^6.0.1",
    "handlebars": "^4.7.8",
    "hbs": "^4.2.0",
    "jsdom": "^24.0.0",
    "luxon": "^3.4.4",
    "playwright": "^1.42.0",
    "puppeteer": "^22.3.0",
    "reflect-metadata": "^0.2.1"
  },
  "eslintConfig": {
    "extends": "@adonisjs/eslint-config/app"
  },
  "prettier": "@adonisjs/prettier-config"
}

Is there something I'm missing in my Docker setup or Playwright configuration that could be causing this issue? Any suggestions or insights would be greatly appreciated.


Solution

  • I don't have access to all of the details of your project, but here's a minimal setup that works and which you can build on for your setup.

    🗎 Dockerfile (I changed from the Alpine base image to the Debian base image because it works better with Chrome system dependencies. Also copy the Chrome executable across from the build stage.)

    FROM node:20.11.1 as base
    
    RUN apt-get update -q && \
        apt-get install -q -y libasound2 libatk-bridge2.0-0 libgtk-4-1 libnss3 xdg-utils
    
    # =============================================================================
    
    FROM base as deps
    WORKDIR /app
    COPY package.json .
    RUN npm install
    
    # =============================================================================
    
    FROM base as build
    WORKDIR /app
    COPY --from=deps /app/node_modules /app/node_modules
    COPY . .
    RUN npm run build
    
    # =============================================================================
    
    FROM base
    ENV NODE_ENV=production
    WORKDIR /app
    COPY --from=deps /app/node_modules /app/node_modules
    COPY --from=deps /root/.cache/ /root/.cache/
    COPY --from=build /app/build /app/bin
    EXPOSE 3333
    CMD ["node", "./bin/server.js"]
    

    🗎 package.json (I used postinstall script to install the Chromium executable.)

    {
      "name": "test",
      "version": "1.0.0",
      "private": true,
      "scripts": {
        "dev": "node ace serve --watch",
        "build": "node ace build --production",
        "start": "node server.js",
        "test": "node ace test",
        "postinstall": "playwright install chromium"
      },
      "devDependencies": {
        "@adonisjs/assembler": "^5.9.6",
        "@japa/preset-adonis": "^1.2.0",
        "@japa/runner": "^2.5.1",
        "@types/proxy-addr": "^2.0.3",
        "@types/source-map-support": "^0.5.10",
        "adonis-preset-ts": "^2.1.0",
        "pino-pretty": "^11.0.0",
        "typescript": "~4.6",
        "youch": "^3.3.3",
        "youch-terminal": "^2.2.3"
      },
      "dependencies": {
        "@adonisjs/core": "^5.9.0",
        "playwright": "^1.42.1",
        "proxy-addr": "^2.0.7",
        "reflect-metadata": "^0.2.1",
        "source-map-support": "^0.5.21"
      }
    }
    

    🗎 server.ts (Just loads a page and checks for correct title.)

    import { chromium } from 'playwright';
    
    async function launchChrome() {
      const browser = await chromium.launch({
        headless: true,
      });
    
      const page = await browser.newPage();
    
      await page.goto('https://example.com');
    
      const expectedTitle = 'Example Domain';
      const title = await page.title();
    
      console.log(`Page Title: ${title}`);
      if (title === expectedTitle) {
        console.log('The page was loaded successfully.');
      } else {
        console.error('The page did not load as expected.');
      }
    
      await browser.close();
    }
    
    launchChrome().catch(err => {
      console.error(err);
      process.exit(1);
    });
    

    enter image description here