---
title: "LHCI Server: Self-Host Lighthouse CI Dashboard"
description: "Deploy your own Lighthouse CI server for historical tracking, build comparisons, and team dashboards. Covers Docker, Heroku, and database setup."
canonical_url: "https://unlighthouse.dev/learn-lighthouse/lighthouse-ci/server"
last_updated: "2026-07-05T10:28:39.116Z"
---

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](https://nodejs.org) 18+ (20+ recommended)
- Database: [SQLite](https://sqlite.org), [MySQL](https://mysql.com), or [PostgreSQL](https://www.postgresql.org/)
- 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**

```bash
# 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**

```bash
# 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](https://docker.com) image makes deployment straightforward.

**Docker Run**

```bash
# 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**

```yaml
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**

```yaml
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:

```text
https://heroku.com/deploy?template=https://github.com/GoogleChrome/lighthouse-ci/tree/main/docs/recipes/heroku-server
```

**Manual Deploy**

```bash
# 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](https://postgresql.org) 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](https://github.com/GoogleChrome/lighthouse-ci/blob/main/docs/server.md) but gets bigger and slower over time.

```bash
lhci server --storage.sqlDatabasePath=/data/lhci.db
```

Environment variable:

```bash
LHCI_STORAGE__SQL_DATABASE_PATH=/data/lhci.db
```

<warning>

**SQLite limitations**: Not optimized for high write volumes. [Azure storage file shares are incompatible with SQLite](https://learn.microsoft.com/en-us/azure/app-service/configure-connect-to-azure-storage#best-practices) - use PostgreSQL for cloud deployments.

</warning>

**MySQL**

Better for multi-server deployments and higher traffic.

```bash
lhci server \
  --storage.sqlDialect=mysql \
  --storage.sqlConnectionUrl=mysql://user:password@host:3306/lhci
```

Environment variables:

```bash
LHCI_STORAGE__SQL_DIALECT=mysql
LHCI_STORAGE__SQL_CONNECTION_URL=mysql://user:password@host:3306/lhci
```

**PostgreSQL**

Recommended for production deployments. [Better query performance](https://www.sqlite.org/whentouse.html) and handles concurrent writes from multiple CI pipelines.

```bash
lhci server \
  --storage.sqlDialect=postgres \
  --storage.sqlConnectionUrl=postgres://user:password@host:5432/lhci
```

Environment variables:

```bash
LHCI_STORAGE__SQL_DIALECT=postgres
LHCI_STORAGE__SQL_CONNECTION_URL=postgres://user:password@host:5432/lhci
```

Connection string format:

```text
postgres://username:password@hostname:port/database?ssl=true
```

## Creating Projects

Use the wizard to create projects and generate tokens.

**Interactive Wizard**

```bash
lhci wizard
```

Follow prompts:

1. Enter server URL (e.g., `https://lhci.yourdomain.com`)
2. Create new project or select existing
3. Project name (e.g., "Marketing Site")
4. Receive build token and admin token

**Output Example**

```text
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:

```bash
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:

```yaml
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:

```bash
# 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**

```js
export default {
  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**

```js
const config2 = {
  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](/learn-lighthouse/lighthouse-ci/configuration) for all upload options.

**Verify Upload**

After CI runs:

```bash
lhci upload \
  --serverBaseUrl=https://lhci.yourdomain.com \
  --token=$LHCI_TOKEN
```

Check logs for:

```text
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**

```bash
LHCI_BASIC_AUTH__USERNAME=admin
LHCI_BASIC_AUTH__PASSWORD=secure-password-here
```

**Docker Compose**

```yaml
services:
  lhci-server:
    image: patrickhulce/lhci-server
    environment:
      - LHCI_BASIC_AUTH__USERNAME=admin
      - LHCI_BASIC_AUTH__PASSWORD=${LHCI_PASSWORD}
```

**CLI Arguments**

```bash
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**

```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**

```text
lhci.yourdomain.com {
  reverse_proxy localhost:9001
}
```

Caddy handles HTTPS automatically with Let's Encrypt.

**Docker with Traefik**

```yaml
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:

```bash
# Vacuum database to reclaim space
sqlite3 /data/lhci.db "VACUUM;"
```

PostgreSQL:

```sql
-- Vacuum and analyze
VACUUM ANALYZE;
```

**Manual Cleanup**

Delete old builds via API:

```bash
# 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](https://docs.newrelic.com/docs/data-apis/manage-data/manage-data-retention/). Disk space is a [common issue](https://github.com/GoogleChrome/lighthouse-ci/issues/794) - plan for automated cleanup.

Set up automated cleanup with cron:

```bash
#!/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:

```text
0 2 * * 0 /path/to/cleanup-old-builds.sh
```

## Updating the Server

Keep LHCI Server up to date for bug fixes and features.

**Docker**

```bash
# Pull latest image
docker pull patrickhulce/lhci-server:latest

# Restart container
docker compose down
docker compose up -d
```

**npm/npx**

```bash
# Update global installation
npm update -g @lhci/server

# Or update in package.json
npm install @lhci/server@latest
```

**Heroku**

```bash
# 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:

```bash
# 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:

```bash
# Wrong
postgres://localhost/lhci

# Right
postgres://username:password@localhost:5432/lhci
```

**Uploads Timeout**

Increase client timeout in `lighthouserc.js`:

```js
const config3 = {
  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):

```bash
node --version
```

Verify database permissions and disk space.

**Port Already in Use**

Change port:

```bash
lhci server --port=9002
```

Or find and kill process on port 9001:

```bash
lsof -ti:9001 | xargs kill -9
```

## Next Steps

- [Configuration guide](/learn-lighthouse/lighthouse-ci/configuration) - Configure assertions and upload options
- [Performance budgets](/learn-lighthouse/lighthouse-ci/budgets) - Set thresholds for your metrics
- [GitHub Actions integration](/learn-lighthouse/lighthouse-ci/github-actions) - Set up CI/CD pipeline
- [Troubleshooting](/learn-lighthouse/lighthouse-ci/troubleshooting) - Debug common issues
- [Core Web Vitals guide](/learn-lighthouse/core-web-vitals) - Understand the metrics you're tracking

## Resources

- [LHCI Server GitHub](https://github.com/GoogleChrome/lighthouse-ci/tree/main/packages/server)
- [Official Docker Image](https://hub.docker.com/r/patrickhulce/lhci-server)
- [Server API Documentation](https://github.com/GoogleChrome/lighthouse-ci/blob/main/docs/server.md)
