Skip to content

patte/alpine-chromium

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

43 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

alpine-chromium

Test and publish

Run headless chromium in a minimal alpine container. This is a fork of jlandure/alpine-chrome adapted to new alpine and chrome versions plus some fonts.

Features:

  • alpine 3.24, chromium 150+
  • Image size: ~800MB
  • Fonts: font-noto, font-noto-emoji, font-wqy-zenhei, ttf-freefont — the generic families (sans-serif, serif, monospace) resolve to Noto; WenQuanYi serves CJK via fallback
  • Works with or without --no-sandbox thanks to a seccomp profile
  • socat to access debug port (because chromium removed remote-debugging-address=0.0.0.0)
  • s6-overlay to manage chromium and socat processes
  • GitHub action to build (weekly) and push the image to ghcr.io
  • Images for linux/amd64 and linux/arm64
  • Test script to verify basic functionality

Image:

ghcr.io/patte/alpine-chromium

Usage

docker run

docker run --rm \
  --security-opt seccomp=./chrome.json \
  -p 9222:9222 \
  ghcr.io/patte/alpine-chromium

docker-compose

services:
  chrome:
    image: ghcr.io/patte/alpine-chromium
    security_opt:
      - seccomp=./chrome.json
    ports:
      - '9222:9222'

Overwriting default chrome args

The Dockerfile sets default args for chromium in CHROMIUM_ARGS. You can overwrite them by setting the CHROMIUM_ARGS environment variable in your docker-compose.yml file. Make sure to keep at least: --headless --remote-debugging-port=9223 --disable-crash-reporter --no-crashpad.

Eg. without security_opt but --no-sandbox:

services:
  chrome:
    image: ghcr.io/patte/alpine-chromium
    ports:
      - '9222:9222'
    environment:
      CHROMIUM_ARGS: '--headless --no-sandbox --no-zygote --remote-debugging-port=9223 --disable-crash-reporter --no-crashpad'

Puppeteer

Connect puppeteer to the running container:

const browser = await puppeteer.connect({
  browserURL: 'http://localhost:9222',
});

Or if you want to use websockets, get webSocketDebuggerUrl from localhost:9222/json/version:

const chromeVersion = await fetch('http://localhost:9222/json/version').then((res) => {
  if (!res.ok) {
    throw new Error(`Can't connect to Chrome: ${res.statusText}`);
  }
  return res.json();
}).catch((e) => {
  console.error('Failed to fetch chrome version', e);
  throw new Error(`Failed to fetch chrome version`);
});

const browser = await puppeteer.connect({
  browserWSEndpoint: chromeVersion.webSocketDebuggerUrl,
});

Hostname

Chrome's remote debugging only accepts connections with the host header set to localhost or an IP address (source). To use a different hostname, resolve it to an IP address first, e.g.:

async function connectToChrome() {
	const chromeHost = env.NODE_ENV === 'production' ? 'systemd-chrome' : 'localhost';

	// resolve hostname to ip, otherwise chrome responds with:
	// "Host header is specified and is not an IP address or localhost."
	const { address: hostname } = await dns.lookup(chromeHost, 4).catch((e) => {
		console.error('Failed to resolve chrome host', e);
		throw new Error(`Failed to resolve chrome host`);
	});

	const chromeVersion = await fetch(`http://${hostname}:9222/json/version`)
		.then((res) => {
			if (!res.ok) {
				throw new Error(`Can't connect to Chrome: ${res.statusText}`);
			}
			return res.json();
		})
		.catch((e) => {
			console.error('Failed to fetch chrome version', e);
			throw new Error(`Failed to fetch chrome version`);
		});

	const browser = await puppeteer.connect({
		browserWSEndpoint: chromeVersion.webSocketDebuggerUrl,
	});

	return browser;
}

Build

To manually build the image, run the following command:

docker build -t localhost/alpine-chromium .

Test

Run the same test that CI executes by calling scripts/test.sh:

./scripts/test.sh

The script builds the image, starts the container with the seccomp profile, waits for localhost:9222/json/version to respond (confirming socat exposes Chromium's debug port), opens https://example.com through the /json/new endpoint, renders a test page (latin text, color emoji, CJK) to a screenshot inside the container and compares it against scripts/test-golden.png (RMSE via ImageMagick, skipped if not installed), and fails on fatal GPU/sandbox errors in the container logs (Chromium can fall back to software compositing and look alive while its GPU process is broken). It then repeats the startup and log check with --disable-gpu args. Customize it by exporting IMAGE_NAME, HOST_PORT, or SKIP_BUILD=1 if you already built the image.

If a chromium or font update legitimately changes rendering, regenerate the golden image with:

UPDATE_GOLDEN=1 ./scripts/test.sh

Credits

Inspired by:

About

chrome headless docker image built on alpine

Topics

Resources

Stars

Watchers

Forks

Packages

Used by

Contributors

Languages