Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
node_modules
npm-debug.log
Dockerfile*
.dockerignore
.git
.gitignore
.github
README.md
CHANGES.md
LICENSE
*.swp
*.log
65 changes: 65 additions & 0 deletions .github/workflows/docker-image.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
name: Build and Publish Docker Image

on:
push:
branches: [ master, main ]
tags: [ 'v*' ]
pull_request:
branches: [ master, main ]

permissions:
contents: read
packages: write

env:
IMAGE_NAME: ghcr.io/${{ github.repository_owner }}/screenie-server

jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Set up QEMU
uses: docker/setup-qemu-action@v3

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Log in to GHCR
if: ${{ github.event_name != 'pull_request' }}
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Extract metadata (tags, labels)
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.IMAGE_NAME }}
tags: |
type=ref,event=branch
type=ref,event=tag
type=sha
type=raw,value=latest,enable=${{ github.ref == 'refs/heads/master' || github.ref == 'refs/heads/main' }}

- name: Build and push
id: build
uses: docker/build-push-action@v6
with:
context: .
push: ${{ github.event_name != 'pull_request' }}
platforms: linux/amd64,linux/arm64
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
build-args: |
PUPPETEER_SKIP_DOWNLOAD=true
cache-from: type=gha
cache-to: type=gha,mode=max

- name: Display image digest
run: echo "Image digest ${{ steps.build.outputs.digest }}"
if: ${{ github.event_name != 'pull_request' }}
13 changes: 13 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,16 @@
# 5.0.0 / 08-04-2026

- _BREAKING_: Minimum Node.js version is now 20
- Replace the old custom Puppeteer pool with `puppeteer-cluster`
- Update Puppeteer to 24.40.0
- Update Koa to 3.2.0 and Winston to 3.19.0
- Tighten request validation and error handling for URL, format, and upstream load failures
- Add structured JSON logging via `SCREENIE_LOG_FORMAT=json`
- Improve graceful shutdown handling for the HTTP server and browser cluster
- Modernize the Docker image for system Chromium, `npm ci --omit=dev`, and non-root execution
- Refresh the Docker publish workflow and keep it limited to `master` and `main`
- Update README examples and configuration notes to match the current runtime behavior

# 4.0.0 / 26-02-2021

- _BREAKING_: Minimum Node 10.18.1
Expand Down
52 changes: 26 additions & 26 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,35 +1,35 @@
FROM node:14-alpine3.12
FROM node:20-bookworm-slim AS base

ARG PUPPETEER_SKIP_DOWNLOAD=true
ENV SCREENIE_VERSION=5.0.0 \
SCREENIE_CHROMIUM_ARGS="--no-sandbox" \
PUPPETEER_SKIP_DOWNLOAD=${PUPPETEER_SKIP_DOWNLOAD} \
PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=${PUPPETEER_SKIP_DOWNLOAD} \
NODE_ENV=production \
SCREENIE_CHROMIUM_EXEC=/usr/bin/chromium

# Install Chromium, fonts, and a minimal init.
RUN apt-get update && apt-get install -y \
chromium \
fonts-liberation \
fonts-noto-cjk \
fonts-freefont-ttf \
ca-certificates \
dumb-init \
--no-install-recommends && rm -rf /var/lib/apt/lists/*

ARG TARGETPLATFORM

ENV SCREENIE_VERSION=4.0.0
ENV SCREENIE_CHROMIUM_ARGS=--no-sandbox
ENV SCREENIE_CHROMIUM_EXEC=/usr/lib/chromium/chrome
ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true

RUN mkdir -p /usr/src/app
WORKDIR /usr/src/app

# Installs latest Chromium package
RUN apk update && apk upgrade && \
apk add --no-cache \
chromium \
nss \
freetype \
harfbuzz \
ttf-freefont \
font-noto-cjk \
git
COPY package.json package-lock.json ./

RUN if [ "$TARGETPLATFORM" = "linux/amd64" ]; then \
wget -O /usr/local/bin/dumb-init https://github.com/Yelp/dumb-init/releases/download/v1.2.5/dumb-init_1.2.5_x86_64; else \
wget -O /usr/local/bin/dumb-init https://github.com/Yelp/dumb-init/releases/download/v1.2.5/dumb-init_1.2.5_aarch64; fi \
&& chmod +x /usr/local/bin/dumb-init
RUN npm ci --omit=dev && npm cache clean --force

ENTRYPOINT ["dumb-init"]
COPY src ./src

RUN npm install -g screenie-server@${SCREENIE_VERSION} --unsafe-perm
RUN chown -R node:node /usr/src/app
USER node

EXPOSE 3000

CMD /usr/local/bin/screenie
ENTRYPOINT ["/usr/bin/dumb-init", "--"]
CMD ["node", "src/server.js"]
106 changes: 89 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,23 +1,44 @@
# screenie-server

HTTP screenshot service based on [Puppeteer](https://github.com/GoogleChrome/puppeteer).
HTTP screenshot service based on [Puppeteer](https://github.com/puppeteer/puppeteer).

Creates a HTTP server using [Koa](https://github.com/koajs/koa), by default on
port 3000. It renders pages and creates screenshots of them on request.
Creates an HTTP server using [Koa](https://github.com/koajs/koa) (default port
`3000`). It renders pages and returns screenshots (PNG/JPEG) or PDFs on demand.
It now uses a shared browser cluster powered by
[puppeteer-cluster](https://github.com/thomasdondorf/puppeteer-cluster) for
efficient parallel processing instead of the old custom pool.

## Installation / Usage

You can install from npm and run the server manually:

```bash
npm install screenie-server
./node_modules/.bin/screenie-server
./node_modules/.bin/screenie
```

Alternatively, we provide a Docker image (built from the
[Dockerfile](Dockerfile)) at [eliksir/screenie-server](https://hub.docker.com/r/eliksir/screenie-server/).
This container is not running in sandbox mode because the Docker image doesn't
support user namespaces.
Or run it without a local install:

```bash
npx screenie-server
```

Alternatively, you can run the published container image from GitHub Container
Registry:

```bash
docker run --rm \
-p 3000:3000 \
-e SCREENIE_LOG_LEVEL=info \
ghcr.io/mailmojo/screenie-server:latest
```

Previous images under `eliksir/screenie-server` on Docker Hub are deprecated.
If you prefer, you can still build your own image locally from the
[Dockerfile](Dockerfile). The container runs Chromium with `--no-sandbox` by
default because typical container runtimes lack the required user namespace /
seccomp configuration. If you run in a hardened environment that supports it,
you may remove `--no-sandbox` via `SCREENIE_CHROMIUM_ARGS`.

## Configuration

Expand All @@ -38,39 +59,90 @@ also set the default format through an environment variable:

* `SCREENIE_DEFAULT_FORMAT`: Default format (default `jpeg`).

The Puppeteer pool can also be customized with environment variables:
The browser cluster can be tuned with environment variables. Legacy variables
(`SCREENIE_POOL_MIN` / `SCREENIE_POOL_MAX`) are still accepted for backward
compatibility; new names are preferred:

* `SCREENIE_POOL_MIN`: Minimum number of Puppeteer instances (default `2`).
* `SCREENIE_POOL_MAX`: Maximum number of Puppeteer instances (default `10`).
* `SCREENIE_CLUSTER_MIN` (or legacy `SCREENIE_POOL_MIN`): Minimum warm contexts (default `2`).
* `SCREENIE_CLUSTER_MAX` (or legacy `SCREENIE_POOL_MAX`): Maximum concurrent cluster tasks (default `10`).

To control the level of logging that will be performed, customize the
`SCREENIE_LOG_LEVEL` environment variable. Supported values are `error`,
`warn`, `info`, `verbose`, `debug`, and `silly`, though only `info` and
`verbose` are currently in use.

* `SCREENIE_LOG_LEVEL`: Logging level (default `info`).
* `SCREENIE_LOG_FORMAT`: Set to `json` for structured JSON logs (default plain / colorized text in non‑production).

To open up file scheme in URL parameter:

* `SCREENIE_ALLOW_FILE_SCHEME`: true (default `false`).
* `SCREENIE_ALLOW_FILE_SCHEME`: Accepts `1`, `true`, `yes`, or `on` to allow `file://` URLs (default `false`).

Delay from the `load` event until the screenshot is taken. This can solve
issues with rendering (i.e. rendering webfonts) not being complete before the
screenshot.

* `SCREENIE_SCREENSHOT_DELAY`: Time in milliseconds (default `50`).
* `SCREENIE_SCREENSHOT_DELAY`: Time in milliseconds (optional; waits after fonts are ready before capture).

And lastly, of course the HTTP port can be customized:

* `SCREENIE_PORT`: HTTP port (default `3000`).
* `SCREENIE_CHROMIUM_ARGS`: Extra Chromium launch args (default `--no-sandbox`).
* `SCREENIE_CHROMIUM_EXEC`: Path to system Chromium (auto-set in Docker image). If unset, Puppeteer will use its bundled browser (unless you skip download).
* `PUPPETEER_SKIP_CHROMIUM_DOWNLOAD` or `PUPPETEER_SKIP_DOWNLOAD`: Set to `true` to rely on system Chromium only (the Docker image does this by default).

## Docker

Run the published image from GitHub Container Registry:

```bash
docker run --rm \
-p 3000:3000 \
-e SCREENIE_LOG_LEVEL=info \
ghcr.io/mailmojo/screenie-server:latest
```

Build locally if you want to customize the image yourself:

```bash
docker build -t screenie-server:local .
docker run --rm \
-p 3000:3000 \
-e SCREENIE_LOG_LEVEL=info \
mailmojo/screenie-server:local
```

Then:

```bash
curl -o example.png "http://localhost:3000/?url=https://example.com&format=png"
```

If you want Puppeteer to download its own Chromium revision instead of using
the system package inside the image, build with:

```bash
docker build --build-arg PUPPETEER_SKIP_DOWNLOAD=false -t mailmojo/screenie-with-bundled .
```

## Contributing

We are open to contributions or suggestions. File issues or suggestions on the
[GitHub issues page](https://github.com/eliksir/screenie-server/issues), and
please do submit a pull request if you have the time to implement an
improvement or bugfix.
We welcome contributions and suggestions.

* Issues: https://github.com/mailmojo/screenie-server/issues
* Pull requests: please include a concise description and, if possible, a short test scenario.
* For significant changes, consider opening an issue first to discuss scope.

## License

Published under the MIT license.

---

### Changelog Highlights (since 5.x)

* Migrated from custom puppeteer pool to `puppeteer-cluster` for improved concurrency.
* Updated runtime requirement to Node.js >= 20.
* Added structured logging option via `SCREENIE_LOG_FORMAT=json`.
* Modernized Docker image (non-root, system Chromium, smaller footprint).
* Added backward-compatible environment variable mapping for cluster sizing.
Loading
Loading