Playwright (Chromium) on FreeBSD. Use as a base image for running browser tests.
| Registry | ghcr.io/daemonless/playwright |
| Source | https://github.com/microsoft/playwright |
| Website | https://playwright.dev/ |
| Tag | Description | Best For |
|---|---|---|
latest |
Upstream Binary. Built from official release. | Most users. Matches Linux Docker behavior. |
services:
playwright:
image: ghcr.io/daemonless/playwright:latest
container_name: playwright
restart: unless-stopped.env:
DIRECTOR_PROJECT=playwright
appjail-director.yml:
options:
- virtualnet: ':<random> default'
- nat:
services:
playwright:
name: playwright
options:
- container: 'boot args:--pull'Makejail:
ARG tag=latest
OPTION overwrite=force
OPTION from=ghcr.io/daemonless/playwright:${tag}
podman run --rm \
ghcr.io/daemonless/playwright:latest- name: Deploy playwright
containers.podman.podman_container:
name: playwright
image: ghcr.io/daemonless/playwright:latest
state: started
restart_policy: alwaysThis image is intended as a base image for running browser automation scripts, not as a long-running service.
Write a script using playwright-core and run it with podman run --rm:
// test.js
const { chromium } = require('playwright-core');
(async () => {
const browser = await chromium.launch({
args: ['--no-sandbox', '--disable-setuid-sandbox'],
});
const context = await browser.newContext({
recordVideo: { dir: '/app/videos/' },
ignoreHTTPSErrors: true,
});
const page = await context.newPage();
try {
await page.goto('https://example.com');
// ... your test logic ...
} finally {
await context.close();
await browser.close();
}
})();podman run --rm \
-v /path/to/test.js:/app/test.js:ro \
-v /path/to/videos:/app/videos \
--network host \
ghcr.io/daemonless/playwright:latest \
node /app/test.jsVideos are recorded as .webm files. Convert to mp4 with:
ffmpeg -i /path/to/videos/*.webm output.mp4Login to a web app and record a video:
// login.js
const { chromium } = require('playwright-core');
(async () => {
const browser = await chromium.launch({
args: ['--no-sandbox', '--disable-setuid-sandbox'],
});
const context = await browser.newContext({
recordVideo: { dir: '/app/videos/', size: { width: 1280, height: 720 } },
ignoreHTTPSErrors: true,
});
const page = await context.newPage();
try {
await page.goto('https://myapp:8181');
await page.waitForLoadState('networkidle');
await page.locator('input[placeholder="Enter your username"]').fill('admin');
await page.locator('input[type="password"]').fill('admin');
await Promise.all([
page.waitForLoadState('networkidle'),
page.getByRole('button', { name: 'Login' }).click(),
]);
// Wait for a post-login element instead of a fixed sleep
await page.waitForTimeout(3000);
} finally {
await context.close();
await browser.close();
}
})();podman run --rm \
-v /path/to/login.js:/app/test.js:ro \
-v /path/to/videos:/app/videos \
--network host \
ghcr.io/daemonless/playwright:latest \
node /app/test.jsChromium and ffmpeg are installed from FreeBSD packages and symlinked into the paths playwright-core expects.
The playwright-core npm package is patched at build time to register freebsd-amd64 as a native platform — no Linux masquerade needed.
git clone https://github.com/daemonless/playwright
pkg install node22 npm-node22 chromium ffmpeg
npm install -g playwright-core
sh playwright/root/build/patch-playwright.sh
doas sh playwright/root/build/setup-ms-playwright.shAdd to your shell profile:
export PLAYWRIGHT_BROWSERS_PATH=/usr/local/libexec/ms-playwright
export PLAYWRIGHT_HOST_PLATFORM_OVERRIDE=freebsd-amd64
export PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1
export NODE_PATH=$(npm root -g)Architectures: amd64
User: bsd (UID/GID via PUID/PGID, defaults to 1000:1000)
Base: FreeBSD 15.0
Need help? Join our Discord community.