├── .gitignore ├── Dockerfile ├── LICENSE ├── README.md ├── examples └── google-screenshot.js ├── sh └── build.sh └── tools ├── fullScreenshot.js ├── fullScreenshotSeries.js ├── full_screenshot ├── full_screenshot_series ├── screenshot ├── screenshot.js ├── screenshotSeries.js └── screenshot_series /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (http://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # Typescript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:12-slim 2 | 3 | RUN apt-get update && \ 4 | apt-get install -yq gconf-service libasound2 libatk1.0-0 libc6 libcairo2 libcups2 libdbus-1-3 \ 5 | libexpat1 libfontconfig1 libgcc1 libgconf-2-4 libgdk-pixbuf2.0-0 libglib2.0-0 libgtk-3-0 libnspr4 \ 6 | libpango-1.0-0 libpangocairo-1.0-0 libstdc++6 libx11-6 libx11-xcb1 libxcb1 libxcomposite1 \ 7 | libxcursor1 libxdamage1 libxext6 libxfixes3 libxi6 libxrandr2 libxrender1 libxss1 libxtst6 \ 8 | fonts-ipafont-gothic fonts-wqy-zenhei fonts-thai-tlwg fonts-kacst ttf-freefont \ 9 | ca-certificates fonts-liberation libappindicator1 libnss3 lsb-release xdg-utils wget && \ 10 | wget https://github.com/Yelp/dumb-init/releases/download/v1.2.1/dumb-init_1.2.1_amd64.deb && \ 11 | dpkg -i dumb-init_*.deb && rm -f dumb-init_*.deb && \ 12 | apt-get clean && apt-get autoremove -y && rm -rf /var/lib/apt/lists/* 13 | 14 | RUN yarn global add puppeteer@1.20.0 && yarn cache clean 15 | 16 | ENV NODE_PATH="/usr/local/share/.config/yarn/global/node_modules:${NODE_PATH}" 17 | 18 | ENV PATH="/tools:${PATH}" 19 | 20 | RUN groupadd -r pptruser && useradd -r -g pptruser -G audio,video pptruser 21 | 22 | COPY --chown=pptruser:pptruser ./tools /tools 23 | 24 | # Set language to UTF8 25 | ENV LANG="C.UTF-8" 26 | 27 | WORKDIR /app 28 | 29 | # Add user so we don't need --no-sandbox. 30 | RUN mkdir /screenshots \ 31 | && mkdir -p /home/pptruser/Downloads \ 32 | && chown -R pptruser:pptruser /home/pptruser \ 33 | && chown -R pptruser:pptruser /usr/local/share/.config/yarn/global/node_modules \ 34 | && chown -R pptruser:pptruser /screenshots \ 35 | && chown -R pptruser:pptruser /app \ 36 | && chown -R pptruser:pptruser /tools 37 | 38 | # Run everything after as non-privileged user. 39 | USER pptruser 40 | 41 | # --cap-add=SYS_ADMIN 42 | # https://docs.docker.com/engine/reference/run/#additional-groups 43 | 44 | ENTRYPOINT ["dumb-init", "--"] 45 | 46 | # CMD ["/usr/local/share/.config/yarn/global/node_modules/puppeteer/.local-chromium/linux-526987/chrome-linux/chrome"] 47 | 48 | CMD ["node", "index.js"] 49 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 alekzonder 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # puppeteer docker image 2 | 3 | docker image with [Google Puppeteer](https://github.com/GoogleChrome/puppeteer) installed 4 | 5 | and [screenshots scripts](#screenshots-tools) 6 | 7 | [![nodesource/node](http://dockeri.co/image/alekzonder/puppeteer)](https://hub.docker.com/r/alekzonder/puppeteer/) 8 | 9 | ## docker tags 10 | 11 | - `latest` 12 | - `1` 13 | - `1.1.1` 14 | - `1.1.0` 15 | - `1.0.0` 16 | - `0` 17 | - `0.13.0` 18 | - `0.12.0` 19 | - `0.11.0` 20 | - `0.10.2` 21 | - `0.10.1` 22 | - `0.10.0` 23 | - `0.9.0` 24 | 25 | ## install 26 | 27 | ``` 28 | docker pull alekzonder/puppeteer:latest 29 | # OR 30 | docker pull alekzonder/puppeteer:1.0.0 31 | # OR 32 | docker pull alekzonder/puppeteer:1 33 | 34 | ``` 35 | 36 | ## before usage 37 | 38 | 39 | 1. you should pass `--no-sandbox, --disable-setuid-sandbox` args when launch browser 40 | 41 | ```js 42 | const puppeteer = require('puppeteer'); 43 | 44 | (async() => { 45 | 46 | const browser = await puppeteer.launch({ 47 | args: [ 48 | '--no-sandbox', 49 | '--disable-setuid-sandbox' 50 | ] 51 | }); 52 | 53 | const page = await browser.newPage(); 54 | 55 | await page.goto('https://www.google.com/', {waitUntil: 'networkidle2'}); 56 | 57 | browser.close(); 58 | 59 | })(); 60 | ``` 61 | 62 | 2. if you got page crash with `BUS_ADRERR` ([chromium issue](https://bugs.chromium.org/p/chromium/issues/detail?id=571394)), increase shm-size on docker run with `--shm-size` argument 63 | 64 | ```bash 65 | docker run --shm-size 1G --rm -v :/app/index.js alekzonder/puppeteer:latest 66 | ``` 67 | 68 | 3. If you're seeing random navigation errors (unreachable url) it's likely due to ipv6 being enabled in docker. Navigation errors are caused by ERR_NETWORK_CHANGED (-21) in chromium. Disable ipv6 in your container using `--sysctl net.ipv6.conf.all.disable_ipv6=1` to fix: 69 | ```bash 70 | docker run --shm-size 1G --sysctl net.ipv6.conf.all.disable_ipv6=1 --rm -v :/app/index.js alekzonder/puppeteer:latest 71 | ``` 72 | 73 | 4. add `--enable-logging` for chrome debug logging http://www.chromium.org/for-testers/enable-logging 74 | 75 | ```js 76 | const puppeteer = require('puppeteer'); 77 | 78 | (async() => { 79 | 80 | const browser = await puppeteer.launch({args: [ 81 | '--no-sandbox', 82 | '--disable-setuid-sandbox', 83 | 84 | // debug logging 85 | '--enable-logging', '--v=1' 86 | ]}); 87 | 88 | 89 | ``` 90 | 91 | 92 | ## usage 93 | 94 | ### mount your script to /app/index.js 95 | 96 | ```bash 97 | docker run --shm-size 1G --rm -v :/app/index.js alekzonder/puppeteer:latest 98 | ``` 99 | 100 | ### custom script from dir 101 | 102 | ```bash 103 | docker run --shm-size 1G --rm \ 104 | -v :/app \ 105 | alekzonder/puppeteer:latest \ 106 | node my_script.js 107 | ``` 108 | 109 | ## screenshots tools 110 | 111 | simple screenshot tools in image 112 | 113 | ```bash 114 | docker run --shm-size 1G --rm -v /tmp/screenshots:/screenshots \ 115 | alekzonder/puppeteer:latest \ 116 | 'https://www.google.com' 1366x768 117 | ``` 118 | 119 | ### screenshot tools syntax 120 | 121 | ` x []` 122 | 123 | * `delay_in_ms`: is optional (defaults to `0`) 124 | * Waits for `delay_in_ms` milliseconds before taking the screenshot 125 | 126 | ### `screenshot` 127 | 128 | ```bash 129 | docker run --shm-size 1G --rm -v /tmp/screenshots:/screenshots \ 130 | alekzonder/puppeteer:latest \ 131 | screenshot 'https://www.google.com' 1366x768 132 | ``` 133 | 134 | output: one line json 135 | 136 | ``` 137 | { 138 | "date":"2017-09-01T05:03:27.464Z", 139 | "timestamp":1504242207, 140 | "filename":"screenshot_1366_768.png", 141 | "width":1366, 142 | "height":768 143 | } 144 | ``` 145 | got screenshot in /tmp/screenshots/screenshot_1366_768.png 146 | 147 | ### `full_screenshot` 148 | 149 | save full screenshot of page 150 | 151 | ```bash 152 | docker run --shm-size 1G --rm -v /tmp/screenshots:/screenshots \ 153 | alekzonder/puppeteer:latest \ 154 | full_screenshot 'https://www.google.com' 1366x768 155 | ``` 156 | 157 | ### `screenshot_series`, `full_screenshot_series` 158 | 159 | adds datetime in ISO format into filename 160 | 161 | useful for cron screenshots 162 | 163 | ```bash 164 | docker run --shm-size 1G --rm -v /tmp/screenshots:/screenshots \ 165 | alekzonder/puppeteer:latest \ 166 | screenshot_series 'https://www.google.com' 1366x768 167 | ``` 168 | 169 | ```bash 170 | docker run --shm-size 1G --rm -v /tmp/screenshots:/screenshots \ 171 | alekzonder/puppeteer:latest \ 172 | full_screenshot_series 'https://www.google.com' 1366x768 173 | ``` 174 | 175 | ``` 176 | 2017-09-01T05:08:55.027Z_screenshot_1366_768.png 177 | # OR 178 | 2017-09-01T05:08:55.027Z_full_screenshot_1366_768.png 179 | ``` 180 | -------------------------------------------------------------------------------- /examples/google-screenshot.js: -------------------------------------------------------------------------------- 1 | const puppeteer = require('puppeteer'); 2 | 3 | (async() => { 4 | 5 | const browser = await puppeteer.launch({ 6 | args: [ 7 | '--no-sandbox', 8 | '--disable-setuid-sandbox' 9 | ] 10 | }); 11 | 12 | const page = await browser.newPage(); 13 | 14 | await page.goto('https://www.google.com/', {waitUntil: 'networkidle'}); 15 | 16 | await page.screenshot({path: 'google.png'}); 17 | 18 | browser.close(); 19 | 20 | })(); 21 | -------------------------------------------------------------------------------- /sh/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | docker build -t alekzonder/puppeteer:latest . 4 | -------------------------------------------------------------------------------- /tools/fullScreenshot.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | function sleep(ms) { 4 | ms = (ms) ? ms : 0; 5 | return new Promise(resolve => {setTimeout(resolve, ms);}); 6 | } 7 | 8 | process.on('uncaughtException', (error) => { 9 | console.error(error); 10 | process.exit(1); 11 | }); 12 | 13 | process.on('unhandledRejection', (reason, p) => { 14 | console.error(reason, p); 15 | process.exit(1); 16 | }); 17 | 18 | const puppeteer = require('puppeteer'); 19 | 20 | // console.log(process.argv); 21 | 22 | if (!process.argv[2]) { 23 | console.error('ERROR: no url arg\n'); 24 | 25 | console.info('for example:\n'); 26 | console.log(' docker run --shm-size 1G --rm -v /tmp:/screenshots \\'); 27 | console.log(' alekzonder/puppeteer:latest screenshot \'https://www.google.com\'\n'); 28 | process.exit(1); 29 | } 30 | 31 | var url = process.argv[2]; 32 | 33 | var now = new Date(); 34 | 35 | var dateStr = now.toISOString(); 36 | 37 | var width = 800; 38 | var height = 600; 39 | 40 | if (typeof process.argv[3] === 'string') { 41 | var [width, height] = process.argv[3].split('x').map(v => parseInt(v, 10)); 42 | } 43 | 44 | var delay = 0; 45 | 46 | if (typeof process.argv[4] === 'string') { 47 | delay = parseInt(process.argv[4], 10); 48 | } 49 | 50 | var isMobile = false; 51 | 52 | let filename = `full_screenshot_${width}_${height}.png`; 53 | 54 | (async() => { 55 | 56 | const browser = await puppeteer.launch({ 57 | args: [ 58 | '--no-sandbox', 59 | '--disable-setuid-sandbox' 60 | ] 61 | }); 62 | 63 | const page = await browser.newPage(); 64 | 65 | page.setViewport({ 66 | width, 67 | height, 68 | isMobile 69 | }); 70 | 71 | await page.goto(url, {waitUntil: 'networkidle2'}); 72 | 73 | await sleep(delay); 74 | 75 | await page.screenshot({path: `/screenshots/${filename}`, fullPage: true}); 76 | 77 | browser.close(); 78 | 79 | console.log( 80 | JSON.stringify({ 81 | date: dateStr, 82 | timestamp: Math.floor(now.getTime() / 1000), 83 | filename, 84 | width, 85 | height 86 | }) 87 | ); 88 | 89 | })(); 90 | -------------------------------------------------------------------------------- /tools/fullScreenshotSeries.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | function sleep(ms) { 4 | ms = (ms) ? ms : 0; 5 | return new Promise(resolve => {setTimeout(resolve, ms);}); 6 | } 7 | 8 | process.on('uncaughtException', (error) => { 9 | console.error(error); 10 | process.exit(1); 11 | }); 12 | 13 | process.on('unhandledRejection', (reason, p) => { 14 | console.error(reason, p); 15 | process.exit(1); 16 | }); 17 | 18 | const puppeteer = require('puppeteer'); 19 | 20 | // console.log(process.argv); 21 | 22 | if (!process.argv[2]) { 23 | console.error('ERROR: no url arg\n'); 24 | 25 | console.info('for example:\n'); 26 | console.log(' docker run --shm-size 1G --rm -v /tmp:/screenshots \\'); 27 | console.log(' alekzonder/puppeteer:latest screenshot \'https://www.google.com\'\n'); 28 | process.exit(1); 29 | } 30 | 31 | var url = process.argv[2]; 32 | 33 | var now = new Date(); 34 | 35 | var dateStr = now.toISOString(); 36 | 37 | var width = 800; 38 | var height = 600; 39 | 40 | if (typeof process.argv[3] === 'string') { 41 | var [width, height] = process.argv[3].split('x').map(v => parseInt(v, 10)); 42 | } 43 | 44 | var delay = 0; 45 | 46 | if (typeof process.argv[4] === 'string') { 47 | delay = parseInt(process.argv[4], 10); 48 | } 49 | 50 | var isMobile = false; 51 | 52 | let filename = `${dateStr}_full_screenshot_${width}_${height}.png`; 53 | 54 | (async() => { 55 | 56 | const browser = await puppeteer.launch({ 57 | args: [ 58 | '--no-sandbox', 59 | '--disable-setuid-sandbox' 60 | ] 61 | }); 62 | 63 | const page = await browser.newPage(); 64 | 65 | page.setViewport({ 66 | width, 67 | height, 68 | isMobile 69 | }); 70 | 71 | await page.goto(url, {waitUntil: 'networkidle2'}); 72 | 73 | await sleep(delay); 74 | 75 | await page.screenshot({path: `/screenshots/${filename}`, fullPage: true}); 76 | 77 | browser.close(); 78 | 79 | console.log( 80 | JSON.stringify({ 81 | date: dateStr, 82 | timestamp: Math.floor(now.getTime() / 1000), 83 | filename, 84 | width, 85 | height 86 | }) 87 | ); 88 | 89 | })(); 90 | -------------------------------------------------------------------------------- /tools/full_screenshot: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | env node /tools/fullScreenshot.js "$@" 4 | -------------------------------------------------------------------------------- /tools/full_screenshot_series: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | env node /tools/fullScreenshotSeries.js "$@" 4 | -------------------------------------------------------------------------------- /tools/screenshot: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | env node /tools/screenshot.js "$@" 4 | -------------------------------------------------------------------------------- /tools/screenshot.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | function sleep(ms) { 4 | ms = (ms) ? ms : 0; 5 | return new Promise(resolve => {setTimeout(resolve, ms);}); 6 | } 7 | 8 | process.on('uncaughtException', (error) => { 9 | console.error(error); 10 | process.exit(1); 11 | }); 12 | 13 | process.on('unhandledRejection', (reason, p) => { 14 | console.error(reason, p); 15 | process.exit(1); 16 | }); 17 | 18 | const puppeteer = require('puppeteer'); 19 | 20 | // console.log(process.argv); 21 | 22 | if (!process.argv[2]) { 23 | console.error('ERROR: no url arg\n'); 24 | 25 | console.info('for example:\n'); 26 | console.log(' docker run --shm-size 1G --rm -v /tmp:/screenshots \\'); 27 | console.log(' alekzonder/puppeteer:latest screenshot \'https://www.google.com\'\n'); 28 | process.exit(1); 29 | } 30 | 31 | var url = process.argv[2]; 32 | 33 | var now = new Date(); 34 | 35 | var dateStr = now.toISOString(); 36 | 37 | var width = 800; 38 | var height = 600; 39 | 40 | if (typeof process.argv[3] === 'string') { 41 | var [width, height] = process.argv[3].split('x').map(v => parseInt(v, 10)); 42 | } 43 | 44 | var delay = 0; 45 | 46 | if (typeof process.argv[4] === 'string') { 47 | delay = parseInt(process.argv[4], 10); 48 | } 49 | 50 | var isMobile = false; 51 | 52 | let filename = `screenshot_${width}_${height}.png`; 53 | 54 | (async() => { 55 | 56 | const browser = await puppeteer.launch({ 57 | args: [ 58 | '--no-sandbox', 59 | '--disable-setuid-sandbox' 60 | ] 61 | }); 62 | 63 | const page = await browser.newPage(); 64 | 65 | page.setViewport({ 66 | width, 67 | height, 68 | isMobile 69 | }); 70 | 71 | await page.goto(url, {waitUntil: 'networkidle2'}); 72 | 73 | await sleep(delay); 74 | 75 | await page.screenshot({path: `/screenshots/${filename}`, fullPage: false}); 76 | 77 | browser.close(); 78 | 79 | console.log( 80 | JSON.stringify({ 81 | date: dateStr, 82 | timestamp: Math.floor(now.getTime() / 1000), 83 | filename, 84 | width, 85 | height 86 | }) 87 | ); 88 | 89 | })(); 90 | -------------------------------------------------------------------------------- /tools/screenshotSeries.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | function sleep(ms) { 4 | ms = (ms) ? ms : 0; 5 | return new Promise(resolve => {setTimeout(resolve, ms);}); 6 | } 7 | 8 | process.on('uncaughtException', (error) => { 9 | console.error(error); 10 | process.exit(1); 11 | }); 12 | 13 | process.on('unhandledRejection', (reason, p) => { 14 | console.error(reason, p); 15 | process.exit(1); 16 | }); 17 | 18 | const puppeteer = require('puppeteer'); 19 | 20 | // console.log(process.argv); 21 | 22 | if (!process.argv[2]) { 23 | console.error('ERROR: no url arg\n'); 24 | 25 | console.info('for example:\n'); 26 | console.log(' docker run --shm-size 1G --rm -v /tmp:/screenshots \\'); 27 | console.log(' alekzonder/puppeteer:latest screenshot \'https://www.google.com\'\n'); 28 | process.exit(1); 29 | } 30 | 31 | var url = process.argv[2]; 32 | 33 | var now = new Date(); 34 | 35 | var dateStr = now.toISOString(); 36 | 37 | var width = 800; 38 | var height = 600; 39 | 40 | if (typeof process.argv[3] === 'string') { 41 | var [width, height] = process.argv[3].split('x').map(v => parseInt(v, 10)); 42 | } 43 | 44 | var delay = 0; 45 | 46 | if (typeof process.argv[4] === 'string') { 47 | delay = parseInt(process.argv[4], 10); 48 | } 49 | 50 | var isMobile = false; 51 | 52 | let filename = `${dateStr}_screenshot_${width}_${height}.png`; 53 | 54 | (async() => { 55 | 56 | const browser = await puppeteer.launch({ 57 | args: [ 58 | '--no-sandbox', 59 | '--disable-setuid-sandbox' 60 | ] 61 | }); 62 | 63 | const page = await browser.newPage(); 64 | 65 | page.setViewport({ 66 | width, 67 | height, 68 | isMobile 69 | }); 70 | 71 | await page.goto(url, {waitUntil: 'networkidle2'}); 72 | 73 | await sleep(delay); 74 | 75 | await page.screenshot({path: `/screenshots/${filename}`, fullPage: false}); 76 | 77 | browser.close(); 78 | 79 | console.log( 80 | JSON.stringify({ 81 | date: dateStr, 82 | timestamp: Math.floor(now.getTime() / 1000), 83 | filename, 84 | width, 85 | height 86 | }) 87 | ); 88 | 89 | })(); 90 | -------------------------------------------------------------------------------- /tools/screenshot_series: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | env node /tools/screenshotSeries.js "$@" 4 | --------------------------------------------------------------------------------