Run local Lighthouse audits in a repeatable way, save both HTML and JSON reports, and summarize the key performance evidence without relying on PageSpeed UI screenshots.
For this repository, always audit the Docker Compose Jekyll server at:
http://127.0.0.1:8080/
Use the Docker Lighthouse image path as the primary path in this repository. Do not start with host npx lighthouse; in this environment host Node/npx has failed with WSL/Node compatibility errors, while femtopixel/google-lighthouse:latest has been available locally.
Known-good local runner shape for this repository:
docker run ... femtopixel/google-lighthouse:latest http://127.0.0.1:8080/ ...
That image’s entrypoint may already be Lighthouse. If so, do not insert a second literal lighthouse after the image name.
Run these first:
docker --version
docker compose version
docker compose ps
If the jekyll service is not running, start it:
docker compose up -d
Then inspect the logs:
docker compose logs --no-color --tail=220 jekyll
Do not trust Lighthouse results if Jekyll/Liquid/Sass errors appear in the logs. Fix those first.
Prefer the existing Docker image:
docker image ls femtopixel/google-lighthouse
docker image inspect femtopixel/google-lighthouse:latest --format ' '
Confirm Lighthouse is available in the image. Try the entrypoint form first:
docker run --rm femtopixel/google-lighthouse:latest --version
If inspection shows the image does not use a Lighthouse entrypoint, use the explicit binary form:
docker run --rm femtopixel/google-lighthouse:latest lighthouse --version
Use the working form consistently in the audit commands. If the image is absent and network is restricted, say that no Lighthouse runner is available instead of trying many unrelated tools.
Use a stable directory and report basename. Use timestamped names when comparing multiple iterations.
mkdir -p lighthouse_results
chmod 777 lighthouse_results
The chmod is intentional: the Lighthouse container may run as a non-host user and otherwise fail to write reports to the bind mount.
For this image, mount the host report directory under the container’s report workspace and use a relative --output-path:
-v "$PWD/lighthouse_results:/home/chrome/reports/lighthouse_results"
--output-path=lighthouse_results/<basename>
This is the form that has worked in this repo. Avoid mixing this with a different mount such as -v "$PWD/lighthouse_results:/home/chrome/reports" unless you also update --output-path consistently.
Common basenames:
lighthouse_results/localhost_8080-mobile-current
lighthouse_results/localhost_8080-desktop-current
lighthouse_results/localhost_8080-mobile-YYYYMMDD-HHMM
lighthouse_results/localhost_8080-desktop-YYYYMMDD-HHMM
Lighthouse writes:
<basename>.report.html
<basename>.report.json
Use Docker as the default command. In this repository, use the entrypoint-is-Lighthouse form:
docker run --rm \
--network host \
--shm-size=1g \
-v "$PWD/lighthouse_results:/home/chrome/reports/lighthouse_results" \
femtopixel/google-lighthouse:latest \
http://127.0.0.1:8080/ \
--emulated-form-factor=mobile \
--screenEmulation.mobile=true \
--screenEmulation.width=390 \
--screenEmulation.height=844 \
--screenEmulation.deviceScaleFactor=3 \
--throttling-method=simulate \
--only-categories=performance,accessibility,best-practices,seo \
--chrome-flags="--headless --no-sandbox --disable-dev-shm-usage" \
--output=html --output=json \
--output-path=lighthouse_results/localhost_8080-mobile-current
If the image does not use a Lighthouse entrypoint, use the explicit binary form:
docker run --rm \
--network host \
--shm-size=1g \
-v "$PWD/lighthouse_results:/home/chrome/reports/lighthouse_results" \
femtopixel/google-lighthouse:latest \
lighthouse http://127.0.0.1:8080/ \
--emulated-form-factor=mobile \
--screenEmulation.mobile=true \
--screenEmulation.width=390 \
--screenEmulation.height=844 \
--screenEmulation.deviceScaleFactor=3 \
--throttling-method=simulate \
--only-categories=performance,accessibility,best-practices,seo \
--chrome-flags="--headless --no-sandbox --disable-dev-shm-usage" \
--output=html --output=json \
--output-path=lighthouse_results/localhost_8080-mobile-current
Expected output files:
lighthouse_results/localhost_8080-mobile-current.report.html
lighthouse_results/localhost_8080-mobile-current.report.json
Use Docker as the default command. In this repository, use the entrypoint-is-Lighthouse form:
docker run --rm \
--network host \
--shm-size=1g \
-v "$PWD/lighthouse_results:/home/chrome/reports/lighthouse_results" \
femtopixel/google-lighthouse:latest \
http://127.0.0.1:8080/ \
--preset=desktop \
--only-categories=performance,accessibility,best-practices,seo \
--chrome-flags="--headless --no-sandbox --disable-dev-shm-usage" \
--output=html --output=json \
--output-path=lighthouse_results/localhost_8080-desktop-current
Explicit binary variant:
docker run --rm \
--network host \
--shm-size=1g \
-v "$PWD/lighthouse_results:/home/chrome/reports/lighthouse_results" \
femtopixel/google-lighthouse:latest \
lighthouse http://127.0.0.1:8080/ \
--preset=desktop \
--only-categories=performance,accessibility,best-practices,seo \
--chrome-flags="--headless --no-sandbox --disable-dev-shm-usage" \
--output=html --output=json \
--output-path=lighthouse_results/localhost_8080-desktop-current
Expected output files:
lighthouse_results/localhost_8080-desktop-current.report.html
lighthouse_results/localhost_8080-desktop-current.report.json
If desktop Chromium crashes or the tab crashes, rerun with the same --shm-size=1g and --disable-dev-shm-usage flags. Do not trust a report from a crashed run.
Always report the exact command actually used if flags, image entrypoint form, or binary paths changed.
Prefer the running Jekyll container’s Node for this repository. Always cd /srv/jekyll inside the container before requiring report JSON; otherwise require("./lighthouse_results/...") may resolve from the wrong directory.
docker compose exec -T jekyll sh -lc 'cd /srv/jekyll && node -e '\''console.log(process.version)'\'''
Mobile summary with Docker Compose Node:
docker compose exec -T jekyll sh -lc 'cd /srv/jekyll && node -e '\''const r=require("./lighthouse_results/localhost_8080-mobile-current.report.json"); for (const [k,v] of Object.entries(r.categories)) console.log(k, Math.round(v.score*100)); for (const id of ["first-contentful-paint","largest-contentful-paint","total-blocking-time","cumulative-layout-shift","speed-index"]) console.log(id, r.audits[id]?.displayValue);'\'''
Desktop summary with Docker Compose Node:
docker compose exec -T jekyll sh -lc 'cd /srv/jekyll && node -e '\''const r=require("./lighthouse_results/localhost_8080-desktop-current.report.json"); for (const [k,v] of Object.entries(r.categories)) console.log(k, Math.round(v.score*100)); for (const id of ["first-contentful-paint","largest-contentful-paint","total-blocking-time","cumulative-layout-shift","speed-index"]) console.log(id, r.audits[id]?.displayValue);'\'''
Transfer size and request count:
docker compose exec -T jekyll sh -lc 'cd /srv/jekyll && node -e '\''const r=require("./lighthouse_results/localhost_8080-mobile-current.report.json"); const a=r.audits["network-requests"]; const items=a.details?.items||[]; const bytes=items.reduce((s,x)=>s+(x.transferSize||0),0); console.log("requests", items.length); console.log("transfer", Math.round(bytes/1024)+" KiB");'\'''
Font/CSS request check, useful after icon font or stylesheet work:
docker compose exec -T jekyll sh -lc 'cd /srv/jekyll && node -e '\''const r=require("./lighthouse_results/localhost_8080-mobile-current.report.json"); const items=r.audits["network-requests"].details.items||[]; for (const x of items) if (/font|woff|site\.css|subset/.test(x.url)) console.log(x.transferSize, x.url);'\'''
Largest Contentful Paint element:
docker compose exec -T jekyll sh -lc 'cd /srv/jekyll && node -e '\''const r=require("./lighthouse_results/localhost_8080-mobile-current.report.json"); console.log(r.audits["largest-contentful-paint-element"]?.displayValue || "no LCP element display value"); console.log(JSON.stringify(r.audits["largest-contentful-paint-element"]?.details?.items?.[0] || {}, null, 2));'\'''
Top diagnostics by wasted bytes or time:
docker compose exec -T jekyll sh -lc 'cd /srv/jekyll && node -e '\''const r=require("./lighthouse_results/localhost_8080-mobile-current.report.json"); for (const id of ["render-blocking-resources","unused-javascript","unused-css-rules","modern-image-formats","uses-responsive-images","total-byte-weight","bootup-time","mainthread-work-breakdown"]) { const a=r.audits[id]; if (a) console.log(id, a.displayValue || a.score); }'\'''
In the final answer, include:
Do not paste large JSON or HTML report contents.
If Docker socket access fails:
permission denied while trying to connect to the docker API
Request minimal escalation for the Docker command and continue after approval.
If Lighthouse cannot find Chrome, try adding or preserving:
--chrome-flags="--headless --no-sandbox --disable-dev-shm-usage"
If the Lighthouse container cannot write reports:
EACCES: permission denied
Run:
mkdir -p lighthouse_results
chmod 777 lighthouse_results
Then rerun the audit.
If host npx lighthouse fails because WSL/Node is broken, do not spend time fixing host Node. Use the Docker Lighthouse image path above.
If a Docker image command fails because the image entrypoint is already Lighthouse, the symptom is usually that lighthouse is treated like a URL or invalid argument. Remove the extra literal lighthouse and rerun with the entrypoint form.