Docker部署NestJS项目Puppeteer报Target.setAutoAttach错误求助
问题:NestJS + Puppeteer Docker部署出现Protocol error (Target.setAutoAttach): Target closed.
环境与配置
Dockerfile
################### # BUILD FOR LOCAL DEVELOPMENT ################### FROM --platform=linux/amd64 node:18-alpine As development # Create app directory WORKDIR /usr/src/app # Copy application dependency manifests to the container image. # A wildcard is used to ensure copying both package.json AND package-lock.json (when available). # Copying this first prevents re-running npm install on every code change. COPY --chown=node:node package*.json ./ # Install app dependencies using the `npm ci` command instead of `npm install` ENV PUPPETEER_SKIP_DOWNLOAD=true RUN npm ci # Bundle app source COPY --chown=node:node . . # Use the node user from the image (instead of the root user) USER node ################### # BUILD FOR PRODUCTION ################### FROM --platform=linux/amd64 node:18-alpine As build WORKDIR /usr/src/app COPY --chown=node:node package*.json ./ # In order to run `npm run build` we need access to the Nest CLI which is a dev dependency. In the previous development stage we ran `npm ci` which installed all dependencies, so we can copy over the node_modules directory from the development image COPY --chown=node:node --from=development /usr/src/app/node_modules ./node_modules COPY --chown=node:node . . # Run the build command which creates the production bundle RUN npm run build # Set NODE_ENV environment variable ENV NODE_ENV production # Running `npm ci` removes the existing node_modules directory and passing in --only=production ensures that only the production dependencies are installed. This ensures that the node_modules directory is as optimized as possible ENV PUPPETEER_SKIP_DOWNLOAD=true RUN npm ci --only=production && npm cache clean --force USER node ################### # PRODUCTION ################### FROM --platform=linux/amd64 node:18-alpine As production # Copy the bundled code from the build stage to the production image COPY --chown=node:node --from=build /usr/src/app/node_modules ./node_modules COPY --chown=node:node --from=build /usr/src/app/dist ./dist RUN apk add chromium EXPOSE 3010 ENV PORT 3010 ENV PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium-browser # Start the server using the production build CMD [ "node", "dist/main.js" ]
启动错误日志
[Nest] ERROR [ExceptionHandler] Protocol error (Target.setAutoAttach): Target closed. ProtocolError: Protocol error (Target.setAutoAttach): Target closed. at /node_modules/puppeteer-core/lib/cjs/puppeteer/common/Connection.js:104:24 at new Promise (<anonymous>) at Connection.send (/node_modules/puppeteer-core/lib/cjs/puppeteer/common/Connection.js:100:16) at ChromeTargetManager.initialize (/node_modules/puppeteer-core/lib/cjs/puppeteer/common/ChromeTargetManager.js:247:82) at CDPBrowser._attach (/node_modules/puppeteer-core/lib/cjs/puppeteer/common/Browser.js:156:76) at CDPBrowser._create (/node_modules/puppeteer-core/lib/cjs/puppeteer/common/Browser.js:49:23) at ChromeLauncher.launch (/node_modules/puppeteer-core/lib/cjs/puppeteer/node/ChromeLauncher.js:130:53) at async InstanceWrapper.useFactory [as metatype] (/node_modules/@noahqte/nest-puppeteer/dist/puppeteer-core.module.js:38:24) at async Injector.instantiateClass (/node_modules/@nestjs/core/injector/injector.js:355:37) at async callback (/node_modules/@nestjs/core/injector/injector.js:56:34)
相关代码
Service代码
constructor( private appService: AppService, @InjectBrowser() private readonly browser: Browser, ) {} async onSendForm1(data, @Ctx() ctx?: Context) { const page = await this.browser.newPage() await page.setRequestInterception(true) await page.once('request', (interceptedRequest) => { interceptedRequest.continue({ method: 'POST', postData: JSON.stringify(data), headers: { 'Content-Type': 'application/json' } }) }) await page.goto('http://localhost:3010', { waitUntil: 'networkidle2' }) const fileName = Guid.newGuid().toString() await page.pdf({ path: `${this.appService.pathFile}${fileName}.pdf`, scale: 0.9, format: 'A4', landscape: false, pageRanges: '1,3', }) await page.close() await this.browser.close() }
AppModule代码
@Module({ imports: [ PuppeteerModule.forRoot({ pipe: true, isGlobal: true }), ], controllers: [AppController], providers: [AppService], exports: [AppService], })
解决方案
- 添加容器环境下Chromium启动参数:容器中运行Chromium需要禁用沙箱等参数,修改
PuppeteerModule的配置,指定必要的启动选项:
PuppeteerModule.forRoot({ isGlobal: true, launch: { executablePath: process.env.PUPPETEER_EXECUTABLE_PATH, args: [ '--no-sandbox', '--disable-setuid-sandbox', '--disable-dev-shm-usage', '--disable-gpu', '--no-zygote', '--single-process' ], headless: 'new' // 新版本Puppeteer推荐使用该无头模式 } })
- 禁止全局Browser实例被关闭:当前代码中每次调用接口都会关闭全局注入的Browser实例,导致后续请求无法创建新页面,移除
await this.browser.close():
async onSendForm1(data, @Ctx() ctx?: Context) { const page = await this.browser.newPage() try { await page.setRequestInterception(true) await page.once('request', (interceptedRequest) => { interceptedRequest.continue({ method: 'POST', postData: JSON.stringify(data), headers: { 'Content-Type': 'application/json' } }) }) await page.goto('http://localhost:3010', { waitUntil: 'networkidle2' }) const fileName = Guid.newGuid().toString() await page.pdf({ path: `${this.appService.pathFile}${fileName}.pdf`, scale: 0.9, format: 'A4', landscape: false, pageRanges: '1,3', }) } finally { await page.close() // 移除此行:await this.browser.close() } }
- 确保文件目录权限正确:在Dockerfile的production阶段添加目录创建与权限配置,避免生成PDF时权限不足:
# 在production阶段,添加在RUN apk add chromium之后: RUN mkdir -p /usr/src/app/pdf && chown -R node:node /usr/src/app/pdf # 注意:需确保this.appService.pathFile指向该目录
- 验证依赖版本兼容性:确认
nestjs-puppeteer与puppeteer-core版本匹配,避免因版本差异导致的协议通信错误。
内容的提问来源于stack exchange,提问作者Wockeez
相关产品推荐
相关产品推荐

