LHCI Server: Self-Host Lighthouse CI Dashboard
LHCI Server provides a hosted dashboard for your Lighthouse CI results. While the CLI can run locally and output to the console, the server component gives you persistent historical data, trend analysis, and a web UI for comparing builds over time.
What LHCI Server Provides
Historical Tracking
- Store Lighthouse reports across all builds
- Track performance trends over weeks and months
- Compare any two builds side-by-side
- View metric changes across deployments
Team Dashboards
- Centralized view of all projects
- Multiple projects per server instance
- Per-project build tokens for security
- Web UI accessible to entire team
Build Comparison
- Visual diff between baseline and current build
- Identify regressions before they ship
- Track improvement over time
- Export comparison data
Requirements
Runtime
- Node.js 18+ (20+ recommended)
- Database: SQLite, MySQL, or PostgreSQL
- 512MB RAM minimum (1GB+ recommended)
- Storage scales with report retention
Network
- HTTP server accessible to CI runners
- Optional: reverse proxy for HTTPS
- Optional: basic auth for protection
Local Deployment
Quickest way to test LHCI Server locally.
Using npx
# SQLite database (easiest for testing)
npx @lhci/server --storage.sqlDatabasePath=./lhci.db
# Custom port
npx @lhci/server --port=9001 --storage.sqlDatabasePath=./lhci.db
Using npm
# Install globally
npm install -g @lhci/server
# Run with SQLite
lhci server --storage.sqlDatabasePath=./lhci.db
# Run with MySQL
lhci server \
--storage.sqlDialect=mysql \
--storage.sqlConnectionUrl=mysql://user:pass@localhost/lhci
Server runs on http://localhost:9001 by default.
Docker Deployment
Pre-built Docker image makes deployment straightforward.
Docker Run
# SQLite (data persists in volume)
docker run -d \
-p 9001:9001 \
-v lhci-data:/data \
--name lhci-server \
patrickhulce/lhci-server
# With custom port
docker run -d \
-p 8080:9001 \
-v lhci-data:/data \
--name lhci-server \
patrickhulce/lhci-server
Docker Compose
version: '3'
services:
lhci-server:
image: patrickhulce/lhci-server
ports:
- '9001:9001'
volumes:
- lhci-data:/data
environment:
- LHCI_STORAGE__SQL_DATABASE_PATH=/data/lhci.db
restart: unless-stopped
volumes:
lhci-data:
With PostgreSQL
version: '3'
services:
lhci-server:
image: patrickhulce/lhci-server
ports:
- '9001:9001'
environment:
- LHCI_STORAGE__SQL_DIALECT=postgres
- LHCI_STORAGE__SQL_CONNECTION_URL=postgres://lhci:password@postgres:5432/lhci
depends_on:
- postgres
restart: unless-stopped
postgres:
image: postgres:15-alpine
environment:
- POSTGRES_USER=lhci
- POSTGRES_PASSWORD=password
- POSTGRES_DB=lhci
volumes:
- postgres-data:/var/lib/postgresql/data
restart: unless-stopped
volumes:
postgres-data:
Heroku Deployment
Heroku provides managed hosting with automatic HTTPS. Note that Heroku discontinued its free tier in November 2022—all deployments now require paid plans.
One-Click Deploy
Use the official Heroku button:
https://heroku.com/deploy?template=https://github.com/GoogleChrome/lighthouse-ci/tree/main/docs/recipes/heroku-server
Manual Deploy
# Install Heroku CLI
npm install -g heroku
# Create app
heroku create your-lhci-server
# Add PostgreSQL (Essential tier, starts at $5/month)
heroku addons:create heroku-postgresql:essential-0
# Set buildpack
heroku buildpacks:set heroku/nodejs
# Deploy
git clone https://github.com/GoogleChrome/lighthouse-ci.git
cd lighthouse-ci
git subtree push --prefix packages/server heroku main
# Open dashboard
heroku open
Heroku automatically sets DATABASE_URL for PostgreSQL connection.
Database Configuration
LHCI Server supports three database backends.
SQLite (Default)
Best for single-server deployments, testing, and low traffic. Works well for small teams but gets bigger and slower over time.
lhci server --storage.sqlDatabasePath=/data/lhci.db
Environment variable:
LHCI_STORAGE__SQL_DATABASE_PATH=/data/lhci.db
MySQL
Better for multi-server deployments and higher traffic.
lhci server \
--storage.sqlDialect=mysql \
--storage.sqlConnectionUrl=mysql://user:password@host:3306/lhci
Environment variables:
LHCI_STORAGE__SQL_DIALECT=mysql
LHCI_STORAGE__SQL_CONNECTION_URL=mysql://user:password@host:3306/lhci
PostgreSQL
Recommended for production deployments. Better query performance and handles concurrent writes from multiple CI pipelines.
lhci server \
--storage.sqlDialect=postgres \
--storage.sqlConnectionUrl=postgres://user:password@host:5432/lhci
Environment variables:
LHCI_STORAGE__SQL_DIALECT=postgres
LHCI_STORAGE__SQL_CONNECTION_URL=postgres://user:password@host:5432/lhci
Connection string format:
postgres://username:password@hostname:port/database?ssl=true
Creating Projects
Use the wizard to create projects and generate tokens.
Interactive Wizard
lhci wizard
Follow prompts:
- Enter server URL (e.g.,
https://lhci.yourdomain.com) - Create new project or select existing
- Project name (e.g., "Marketing Site")
- Receive build token and admin token
Output Example
Created project Marketing Site (id: abc123)!
Build Token: def456789...
Admin Token: ghi012345...
Use build token in your CI configuration:
LHCI_TOKEN=def456789...
Manual Project Creation
Via API after server is running:
curl -X POST https://lhci.yourdomain.com/v1/projects \
-H "Content-Type: application/json" \
-d '{
"name": "Marketing Site",
"externalUrl": "https://marketing.example.com",
"slug": "marketing-site"
}'
Returns project ID and tokens in JSON response.
Token Security Model
LHCI uses two types of tokens with different permissions.
Build Token
- Used by CI to upload Lighthouse reports
- Can only write data, cannot read
- Safe to use in CI environment variables
- One per project
Example in GitHub Actions:
env:
LHCI_TOKEN: ${{ secrets.LHCI_BUILD_TOKEN }}
Admin Token
- Full read/write access to project
- Can create/delete builds
- Manage project settings
- Keep secure, never commit
Use admin token sparingly:
# Delete old builds (use admin token)
curl -X DELETE https://lhci.yourdomain.com/v1/projects/abc123/builds/789 \
-H "Authorization: Bearer $ADMIN_TOKEN"
Store tokens in:
- CI secrets (GitHub Secrets, GitLab Variables)
- Environment variables
- Secret management tools (Vault, AWS Secrets Manager)
Never hardcode tokens in lighthouserc.js.
Configuring Upload Target
Point your CI configuration to your LHCI Server.
lighthouserc.js
module.exports = {
ci: {
collect: {
url: ['http://localhost:3000'],
numberOfRuns: 3
},
upload: {
target: 'lhci',
serverBaseUrl: 'https://lhci.yourdomain.com',
token: process.env.LHCI_TOKEN // Build token from env
},
assert: {
preset: 'lighthouse:recommended',
assertions: {
'categories:performance': ['error', { minScore: 0.9 }]
}
}
}
}
Upload Options
upload: {
target: 'lhci',
serverBaseUrl: 'https://lhci.yourdomain.com',
token: process.env.LHCI_TOKEN,
// Optional: GitHub App integration
githubAppToken: process.env.LHCI_GITHUB_APP_TOKEN,
// Optional: ignore HTTPS certificate errors (not recommended)
ignoreDuplicateBuildFailure: true
}
See configuration guide for all upload options.
Verify Upload
After CI runs:
lhci upload \
--serverBaseUrl=https://lhci.yourdomain.com \
--token=$LHCI_TOKEN
Check logs for:
Saving CI project Marketing Site (abc123)
Saved LHR to https://lhci.yourdomain.com (build: def456)
Done saving build results to Lighthouse CI
Basic Auth Protection
Add basic authentication to protect your dashboard.
Environment Variables
LHCI_BASIC_AUTH__USERNAME=admin
LHCI_BASIC_AUTH__PASSWORD=secure-password-here
Docker Compose
services:
lhci-server:
image: patrickhulce/lhci-server
environment:
- LHCI_BASIC_AUTH__USERNAME=admin
- LHCI_BASIC_AUTH__PASSWORD=${LHCI_PASSWORD}
CLI Arguments
lhci server \
--basicAuth.username=admin \
--basicAuth.password=secure-password \
--storage.sqlDatabasePath=./lhci.db
Basic auth applies to web UI only. API token authentication still works for uploads.
Uploading with Basic Auth
If your server has basic auth, CI uploads use token authentication and bypass basic auth automatically. No additional configuration needed.
Reverse Proxy Setup
Run LHCI Server behind nginx or Caddy for HTTPS.
Nginx
server {
listen 443 ssl http2;
server_name lhci.yourdomain.com;
ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/key.pem;
location / {
proxy_pass http://localhost:9001;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache_bypass $http_upgrade;
}
}
Caddy
lhci.yourdomain.com {
reverse_proxy localhost:9001
}
Caddy handles HTTPS automatically with Let's Encrypt.
Docker with Traefik
services:
lhci-server:
image: patrickhulce/lhci-server
labels:
- traefik.enable=true
- traefik.http.routers.lhci.rule=Host(`lhci.yourdomain.com`)
- traefik.http.routers.lhci.entrypoints=websecure
- traefik.http.routers.lhci.tls.certresolver=letsencrypt
Storage Considerations
LHCI Server storage grows with report retention.
Storage Estimates
Per build (3 runs):
- Raw Lighthouse reports: ~2-3 MB
- Screenshots: ~500 KB - 1 MB
- Metadata: ~50 KB
Example: 100 builds/month = ~300-400 MB/month
Database Maintenance
SQLite:
# Vacuum database to reclaim space
sqlite3 /data/lhci.db "VACUUM;"
PostgreSQL:
-- Vacuum and analyze
VACUUM ANALYZE;
Manual Cleanup
Delete old builds via API:
# List builds
curl https://lhci.yourdomain.com/v1/projects/abc123/builds \
-H "Authorization: Bearer $ADMIN_TOKEN"
# Delete specific build
curl -X DELETE https://lhci.yourdomain.com/v1/projects/abc123/builds/789 \
-H "Authorization: Bearer $ADMIN_TOKEN"
Retention Policy
Industry standard for performance monitoring: raw metrics for 30 days, aggregated data for 13 months. Disk space is a common issue — plan for automated cleanup.
Set up automated cleanup with cron:
#!/bin/bash
# cleanup-old-builds.sh
# Delete builds older than 90 days
ADMIN_TOKEN="your-admin-token"
SERVER="https://lhci.yourdomain.com"
PROJECT_ID="abc123"
CUTOFF_DATE=$(date -d '90 days ago' +%s)
# Fetch builds and filter by date
curl "$SERVER/v1/projects/$PROJECT_ID/builds" \
-H "Authorization: Bearer $ADMIN_TOKEN" | \
jq -r ".[] | select(.createdAt < $CUTOFF_DATE) | .id" | \
while read BUILD_ID; do
curl -X DELETE "$SERVER/v1/projects/$PROJECT_ID/builds/$BUILD_ID" \
-H "Authorization: Bearer $ADMIN_TOKEN"
echo "Deleted build $BUILD_ID"
done
Cron entry:
0 2 * * 0 /path/to/cleanup-old-builds.sh
Updating the Server
Keep LHCI Server up to date for bug fixes and features.
Docker
# Pull latest image
docker pull patrickhulce/lhci-server:latest
# Restart container
docker compose down
docker compose up -d
npm/npx
# Update global installation
npm update -g @lhci/server
# Or update in package.json
npm install @lhci/server@latest
Heroku
# Pull latest code
git pull origin main
# Deploy
git push heroku main
Database Migrations
LHCI Server handles schema migrations automatically on startup. Backup your database before major version upgrades:
# SQLite backup
cp /data/lhci.db /data/lhci.db.backup
# PostgreSQL backup
pg_dump lhci > lhci-backup.sql
Production Checklist
Before deploying to production:
- Use PostgreSQL or MySQL (not SQLite for multi-server)
- Enable basic auth or place behind VPN
- Set up HTTPS via reverse proxy
- Configure database backups
- Store tokens in secrets manager
- Set up monitoring (uptime, disk space)
- Plan retention policy and cleanup automation
- Document server URL and project IDs for team
- Test upload from CI environment
- Configure GitHub App integration (optional)
Common Deployment Issues
Database Connection Fails
Check connection string format:
# Wrong
postgres://localhost/lhci
# Right
postgres://username:password@localhost:5432/lhci
Uploads Timeout
Increase client timeout in lighthouserc.js:
upload: {
target: 'lhci',
serverBaseUrl: 'https://lhci.yourdomain.com',
token: process.env.LHCI_TOKEN,
uploadUrlTimeout: 120000 // 2 minutes
}
Server Crashes on Start
Check Node version (16+ required):
node --version
Verify database permissions and disk space.
Port Already in Use
Change port:
lhci server --port=9002
Or find and kill process on port 9001:
lsof -ti:9001 | xargs kill -9
Next Steps
- Configuration guide - Configure assertions and upload options
- Performance budgets - Set thresholds for your metrics
- GitHub Actions integration - Set up CI/CD pipeline
- Troubleshooting - Debug common issues
- Core Web Vitals guide - Understand the metrics you're tracking
Resources
- What LHCI Server Provides
- Requirements
- Local Deployment
- Docker Deployment
- Heroku Deployment
- Database Configuration
- Creating Projects
- Token Security Model
- Configuring Upload Target
- Basic Auth Protection
- Reverse Proxy Setup
- Storage Considerations
- Updating the Server
- Production Checklist
- Common Deployment Issues
- Next Steps
- Resources