The LHCI server saves historical Lighthouse data, displays trends in a dashboard, and offers an in-depth build comparison UI to uncover differences between builds.
The LHCI server can be run in any node environment with persistent disk storage or network access to a postgres/mysql database.
- Node v16 LTS
- Database Storage (sqlite, mysql, or postgresql)
If you'd like to run LHCI on your own infrastructure, you'll need a package.json to install the dependencies via npm. See the Heroku and docker recipes for more examples on how to deploy the LHCI server.
NOTE: LHCI really needs to own the entire origin to work as expected. However, if you can't do that and have to operate over a proxy, you can follow the example of how to configure LHCI on a subpath.
npm install @lhci/server sqlite3
const {createServer} = require('@lhci/server');
console.log('Starting server...');
createServer({
port: process.env.PORT,
storage: {
storageMethod: 'sql',
sqlDialect: 'sqlite',
sqlDatabasePath: '/path/to/db.sql',
},
}).then(({port}) => console.log('LHCI listening on port', port));
See the advanced server configuration examples for more of the available options.
You can also try out running the server locally via the CLI.
npm install -D @lhci/cli @lhci/server sqlite3
npx lhci server --storage.storageMethod=sql --storage.sqlDialect=sqlite --storage.sqlDatabasePath=./db.sql
LHCI server can be deployed to Heroku in just a few minutes. See the Heroku recipe for details.
LHCI server is ready to go in a docker container that can be deployed to AWS/GCP/Azure in just a few minutes. See the Docker recipe for details.
You can also try out the docker server locally.
docker volume create lhci-data
docker container run --publish 9001:9001 --mount='source=lhci-data,target=/data' --detach patrickhulce/lhci-server
The default security model of LHCI server is two-tiered:
- Anyone with HTTP access can view and create data.
- Anyone with the admin token for a project can edit or delete data within that project.
In other words, the server has very weak authentication mechanisms by default and anyone with HTTP access to the server will be able to view your Lighthouse results and upload data to the server. If your Lighthouse reports contain sensitive information or you would like to prevent unauthorized users from uploading builds, you have a few options outlined below.
LHCI has two built-in authentication mechanisms enabled by default: the build token and the admin token.
The build token allows a user to upload new data to a particular project, but does not allow the destruction or editing of any historical data. If your project is open source and you want to collect Lighthouse data on pull requests from external contributors then you should consider the build token to be public knowledge.
The admin token allows a user to edit or delete data from a particular project. The admin token should only be shared with trusted users and never placed in the CI environment, even in open source projects with external contributors. Anyone with the admin token can delete the entire project's data.
All other actions on the server including listing projects, viewing project and build data, and creating new projects are open to anyone with HTTP access. If you'd like to protect these actions, see the other two authentication mechanisms.
If you forget either of these tokens you will need to connect directly to the storage of the server to reset them using the lhci wizard
command.
You can secure the Lighthouse CI server with HTTP Basic auth to prevent unauthorized users from wandering into your server. It's built-in to the LHCI server and can be configured with just two properties.
lhci server --basicAuth.username=myusername --basicAuth.password=mypassword
Be sure to set the same credentials in your upload step to be able to continue sending builds to your server.
lhci autorun --upload.basicAuth.username=myusername --upload.basicAuth.password=mypassword
When navigating to your server's URL to view results you'll be presented with the classic browser login UI. Provide the same username and password configured on the server and you're in!
The server also exposes the created express app directly, so you can add your own custom middleware, routes, anything you like!
See Using Express Middleware documentation for more details on how to use express.
const express = require('express');
const lhci = require('@lhci/server');
(async () => {
const app = express();
const {app: lhciApp} = await lhci.createApp({
storage: {
storageMethod: 'sql',
sqlDialect: 'sqlite',
// see configuration...
},
});
app.use((req, res, next) => handleCustomAuthentication(req, res, next));
app.use(lhciApp);
app.listen();
})();
const fastify = require('fastify');
const lhci = require('@lhci/server');
(async () => {
const {app} = await createApp({
// you need that for use fastify
useBodyParser: false,
storage: {
storageMethod: 'sql',
sqlDialect: 'sqlite',
// see configuration...
},
});
const fastify = require('fastify')({
logger: true
})
fastify.all(
'/*',
async (req, reply) => {
// you need to clone the body for express or this will be emit error
req.raw.body = req.body;
await app(req.raw, reply.raw);
// don't forget that or fastify will be not respond
reply.hijack();
}
);
// Run the server!
fastify.listen({ port: 3000 }, function (err, address) {
if (err) {
fastify.log.error(err)
process.exit(1)
}
// Server is now listening on ${address}
})
})();
You can also protect your server through firewall rules to prevent it from being accessed from outside your internal network. Refer to your infrastructure provider's documentation on how to setup firewall rules to block external IP addresses from accessing the server. Don't forget to allow your CI machines!
If you are hosting your own lighthouse report viewer instead of using the default viewer, you can add --viewer.origin
to your lighthouse server configuration. Eg.
const {createServer} = require('@lhci/server');
console.log('Starting server...');
createServer({
...
viewer: {
origin: 'https://viewer-url' // Default: https://googlechrome.github.io
}
}).then(({port}) => console.log('LHCI listening on port', port));