Lighthouse CI with GitLab CI/CD: Setup Guide
Lighthouse CI integrates seamlessly with GitLab CI/CD to run performance audits on every merge request. This guide covers setup, configuration, and best practices for GitLab pipelines.
Quick Start
Basic .gitlab-ci.yml to audit a static site:
lighthouse:
image: cypress/browsers:node-22.12.0-chrome-131.0.6778.204-1-ff-134.0-edge-131.0.2903.112-1
stage: test
script:
- npm install -g @lhci/cli@0.15.x
- lhci autorun
artifacts:
when: always
paths:
- .lighthouseci/
expire_in: 7 days
Create lighthouserc.js:
module.exports = {
ci: {
collect: {
staticDistDir: './dist',
chromePath: '/usr/bin/google-chrome',
chromeFlags: '--no-sandbox'
},
upload: {
target: 'temporary-public-storage'
}
}
}
Why --no-sandbox?
GitLab CI runners require Chrome's --no-sandbox flag because Docker's default seccomp profile blocks the syscalls Chrome needs to create its own namespace sandbox. The container itself provides isolation, creating a conflict.
Without it, Chrome fails to launch:
Failed to launch Chrome: spawn EACCES
Always include chromeFlags: '--no-sandbox' in your configuration.
--no-sandbox in trusted CI environments running your own code. If auditing untrusted websites, malicious pages could escape the container. For maximum security, use Jess Frazelle's Chrome seccomp profile which allows only the specific syscalls Chrome needs.Testing Different Targets
Static Build Output
Audit files in dist/ or build/:
lighthouse:
image: cypress/browsers:node-22.12.0-chrome-131.0.6778.204-1-ff-134.0-edge-131.0.2903.112-1
stage: test
script:
- npm ci
- npm run build
- npm install -g @lhci/cli@0.15.x
- lhci autorun
artifacts:
paths:
- .lighthouseci/
// lighthouserc.js
module.exports = {
ci: {
collect: {
staticDistDir: './dist',
chromePath: '/usr/bin/google-chrome',
chromeFlags: '--no-sandbox'
}
}
}
Live URLs
Test production or staging URLs:
module.exports = {
ci: {
collect: {
url: [
'https://example.com',
'https://example.com/about',
'https://example.com/products'
],
chromePath: '/usr/bin/google-chrome',
chromeFlags: '--no-sandbox'
}
}
}
Local Development Server
Start server, run audits, shut down:
module.exports = {
ci: {
collect: {
startServerCommand: 'npm run serve',
startServerReadyPattern: 'Server listening on',
url: ['http://localhost:8080'],
chromePath: '/usr/bin/google-chrome',
chromeFlags: '--no-sandbox'
}
}
}
The CLI automatically kills the server after audits complete.
Adding Assertions
Fail the pipeline if performance budgets aren't met:
module.exports = {
ci: {
collect: {
staticDistDir: './dist',
chromePath: '/usr/bin/google-chrome',
chromeFlags: '--no-sandbox'
},
assert: {
assertions: {
'categories:performance': ['error', { minScore: 0.9 }],
'categories:accessibility': ['error', { minScore: 0.9 }],
'categories:best-practices': ['error', { minScore: 0.9 }],
'categories:seo': ['error', { minScore: 0.9 }],
'first-contentful-paint': ['error', { maxNumericValue: 2000 }],
'largest-contentful-paint': ['error', { maxNumericValue: 2500 }],
'cumulative-layout-shift': ['error', { maxNumericValue: 0.1 }],
'total-blocking-time': ['error', { maxNumericValue: 300 }]
}
},
upload: {
target: 'temporary-public-storage'
}
}
}
Pipeline fails if any assertion fails. See budgets for assertion syntax.
Storing Reports as Artifacts
Save HTML reports for download:
lighthouse:
image: cypress/browsers:node-22.12.0-chrome-131.0.6778.204-1-ff-134.0-edge-131.0.2903.112-1
stage: test
script:
- npm install -g @lhci/cli@0.15.x
- lhci autorun --upload.target=filesystem --upload.outputDir=./lhci-reports
artifacts:
when: always
paths:
- lhci-reports/
- .lighthouseci/
expire_in: 30 days
Reports appear in GitLab's merge request artifacts.
Testing Review Apps
Audit ephemeral Review Apps using GitLab environment variables:
lighthouse:
image: cypress/browsers:node-22.12.0-chrome-131.0.6778.204-1-ff-134.0-edge-131.0.2903.112-1
stage: test
rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
script:
- npm install -g @lhci/cli@0.15.x
- lhci autorun --collect.url=$CI_ENVIRONMENT_URL
environment:
name: review/$CI_COMMIT_REF_NAME
url: https://$CI_COMMIT_REF_SLUG.review.example.com
Or configure in lighthouserc.js:
module.exports = {
ci: {
collect: {
url: [process.env.CI_ENVIRONMENT_URL],
chromePath: '/usr/bin/google-chrome',
chromeFlags: '--no-sandbox'
}
}
}
Reducing Variance with Multiple Runs
Run Lighthouse multiple times and use median values:
module.exports = {
ci: {
collect: {
numberOfRuns: 5,
staticDistDir: './dist',
chromePath: '/usr/bin/google-chrome',
chromeFlags: '--no-sandbox'
}
}
}
Increases job duration but reduces false negatives from variance. Google's research shows 5 runs is twice as stable as 1 run. GitLab runners are generally stable, so 3 runs (37% variance reduction) is usually sufficient.
Full Production Pipeline
Complete example with build, test, and artifact storage:
stages:
- build
- test
variables:
NODE_ENV: production
build:
stage: build
image: node:20
script:
- npm ci
- npm run build
artifacts:
paths:
- dist/
expire_in: 1 hour
lighthouse:
stage: test
image: cypress/browsers:node-22.12.0-chrome-131.0.6778.204-1-ff-134.0-edge-131.0.2903.112-1
dependencies:
- build
rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
- if: '$CI_COMMIT_BRANCH == "main"'
script:
- npm install -g @lhci/cli@0.15.x
- lhci autorun
artifacts:
when: always
paths:
- .lighthouseci/
- lhci-reports/
expire_in: 30 days
// lighthouserc.js
module.exports = {
ci: {
collect: {
staticDistDir: './dist',
numberOfRuns: 3,
chromePath: '/usr/bin/google-chrome',
chromeFlags: '--no-sandbox'
},
assert: {
preset: 'lighthouse:recommended',
assertions: {
'categories:performance': ['error', { minScore: 0.9 }],
'uses-responsive-images': 'off',
'offscreen-images': 'off'
}
},
upload: {
target: 'filesystem',
outputDir: './lhci-reports'
}
}
}
Lighthouse CI Server Integration
Upload to a persistent Lighthouse CI server for historical tracking:
lighthouse:
image: cypress/browsers:node-22.12.0-chrome-131.0.6778.204-1-ff-134.0-edge-131.0.2903.112-1
stage: test
script:
- npm install -g @lhci/cli@0.15.x
- lhci autorun --upload.serverBaseUrl=$LHCI_SERVER_URL --upload.token=$LHCI_BUILD_TOKEN
Set LHCI_SERVER_URL and LHCI_BUILD_TOKEN as CI/CD variables in GitLab project settings. See server setup for details.
Troubleshooting
Chrome Won't Launch
Error:
Failed to launch Chrome: spawn /usr/bin/google-chrome ENOENT
Fix: Use cypress/browsers image which includes Chrome, or install Chrome manually:
script:
- apt-get update
- apt-get install -y wget gnupg
- wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | gpg --dearmor -o /usr/share/keyrings/google-chrome.gpg
- echo "deb [signed-by=/usr/share/keyrings/google-chrome.gpg] http://dl.google.com/linux/chrome/deb/ stable main" > /etc/apt/sources.list.d/google.list
- apt-get update
- apt-get install -y google-chrome-stable
- npm install -g @lhci/cli@0.15.x
- lhci autorun
Sandbox Errors
Error:
Failed to move to new namespace: PID namespaces supported, Network namespace supported, but failed: errno = Operation not permitted
Fix: Add --no-sandbox to chrome flags:
chromeFlags: '--no-sandbox'
Out of Memory
Error:
FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory
Fix: Increase memory in .gitlab-ci.yml:
variables:
NODE_OPTIONS: --max-old-space-size=4096
Or reduce numberOfRuns:
collect: {
numberOfRuns: 1
}
Shared Memory Issues
Error:
Browser tab has unexpectedly crashed
Fix: Docker's default 64MB /dev/shm is insufficient for Chrome. Either use --disable-dev-shm-usage flag (writes to disk, slower) or increase shared memory:
services:
- name: docker:dind
command: [--shm-size=2g]
Job Timeout
GitLab default timeout is 1 hour. For numberOfRuns: 5, jobs may timeout. Reduce runs or increase timeout:
lighthouse:
timeout: 2 hours
Reports Not Generated
Ensure upload.target is set:
upload: {
target: 'filesystem',
outputDir: './lhci-reports'
}
And artifacts path matches:
artifacts:
paths:
- lhci-reports/
Next Steps
- Configuration reference - All
lighthouserc.jsoptions - Performance budgets - Assertion syntax and examples
- LHCI Server - Historical tracking and comparison
- Troubleshooting - Common issues and fixes
When Budgets Fail
If your assertions fail, use these guides to fix the underlying issues:
- Fix LCP — Largest Contentful Paint optimization
- Fix CLS — Cumulative Layout Shift fixes
- Fix INP — Interaction to Next Paint improvements
- Core Web Vitals Overview — Understanding all metrics