Sitemap

Running Lighthouse CI in a Lightweight Docker Container

7 min readOct 23, 2025

This guide demonstrates how to run Lighthouse CI performance tests for www.pradappandiyan.com using a lightweight Docker container. By containerizing our Lighthouse tests, we ensure consistent, reproducible results across different environments while maintaining a minimal footprint.

Press enter or click to view image in full size

Why Docker for Lighthouse CI?

Benefits

  1. Consistency — Same environment everywhere (local, CI/CD, production)
  2. Isolation — No conflicts with your system’s Chrome or Node versions
  3. Portability — Run anywhere Docker is installed
  4. Reproducibility — Same results across different machines
  5. Clean Setup — No need to install Chrome/Chromium locally

Why Lightweight Matters

Performance testing should be fast and efficient. A bloated Docker image means:

  • ❌ Longer build times
  • ❌ More bandwidth for CI/CD pipelines
  • ❌ Higher storage costs
  • ❌ Slower deployments

Our optimized image addresses all these concerns.

Architecture: How We Built a Lightweight Image

Image Size Breakdown

Final Image Size: ~913MB

┌─────────────────────────────────────────┐
│ Component Size % │
├─────────────────────────────────────────┤
│ Chromium Browser ~650MB 71% │
│ Node.js 18 Alpine ~50MB 5% │
│ Lighthouse CI ~150MB 16% │
│ System Libraries ~63MB 7% │
└─────────────────────────────────────────┘

Why ~900MB is Actually Lightweight

Comparison with alternatives:

SolutionSizeNotesOur Alpine Build~913MBOptimizedNode:18 + Chrome (Debian)~1.8GB2x largerPuppeteer Official~1.2GB30% largerFull Ubuntu + Chrome~2.5GB2.7x larger

The bulk of the size (~650MB) comes from Chromium browser, which is unavoidable for real browser testing. You cannot test a website without an actual browser engine!

Optimization Techniques Applied

1. Multi-Stage Build

We use a two-stage Docker build to separate build dependencies from runtime:

# Stage 1: Build (discarded after)
FROM node:18-alpine AS builder
RUN npm ci --omit=dev --ignore-scripts

# Stage 2: Runtime (final image)
FROM node:18-alpine
COPY --from=builder /app/node_modules ./node_modules

Result: Build tools and npm cache don’t bloat the final image.

2. Alpine Linux Base

Alpine Linux is a security-oriented, lightweight distribution:

  • Base image: ~5MB
  • Full Node.js Alpine: ~50MB
  • Standard Node.js: ~900MB

Savings: ~850MB from base image alone

3. Minimal Dependencies

Only essential Chromium dependencies are installed:

RUN apk add --no-cache \
chromium \
freetype \
harfbuzz \
ca-certificates

Avoided: Unnecessary fonts, documentation, development tools

4. Node Modules Cleanup

Aggressive cleanup of unnecessary files:

# Remove documentation
find node_modules -type f -name "*.md" -delete
# Remove license files
find node_modules -type f -name "LICENSE*" -delete
# Remove source maps
find node_modules -type f -name "*.map" -delete
# Remove test directories
find node_modules -type d -name "test" -exec rm -rf {} +

Savings: ~35–50MB

5. Cache Cleanup

Remove all temporary files and caches:

RUN rm -rf /var/cache/apk/* \
/tmp/* \
/usr/share/man \
/usr/share/doc

Savings: ~10–20MB

6. Production-Only Dependencies

Using npm ci --omit=dev ensures no development dependencies:

{
"dependencies": {
"@lhci/cli": "^0.13.0"
}
}

Savings: ~100MB of dev dependencies

7. Security Best Practices

Running as non-root user (doesn’t reduce size, but improves security):

RUN addgroup -g 1001 -S nodejs && \
adduser -S lighthouse -u 1001
USER lighthouse

Quick Start: Running the Test Locally

Prerequisites

  • Docker installed (Get Docker)
  • 2GB RAM available
  • Internet connection (to test the website)

Docker File

# Ultra-lightweight multi-stage build
# Stage 1: Build stage
FROM node:18-alpine AS builder

WORKDIR /app

# Copy only package files first (better caching)
COPY package*.json ./

# Install dependencies with optimizations
RUN npm ci --omit=dev --ignore-scripts && \
npm cache clean --force && \
# Remove unnecessary files from node_modules
find node_modules -type f -name "*.md" -delete && \
find node_modules -type f -name "LICENSE*" -delete && \
find node_modules -type f -name "*.map" -delete && \
find node_modules -type d -name "test" -exec rm -rf {} + 2>/dev/null || true && \
find node_modules -type d -name "tests" -exec rm -rf {} + 2>/dev/null || true && \
find node_modules -type d -name "docs" -exec rm -rf {} + 2>/dev/null || true

# Stage 2: Runtime stage (minimal)
FROM node:18-alpine

# Install ONLY critical Chromium dependencies (minimal set)
RUN apk add --no-cache \
chromium \
# Only essential libraries
freetype \
harfbuzz \
ca-certificates \
# Remove unnecessary font packages
&& \
# Remove package manager cache
rm -rf /var/cache/apk/* \
/tmp/* \
/usr/share/man \
/usr/share/doc

# Set Chrome environment variables
ENV CHROME_BIN=/usr/bin/chromium-browser \
PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true \
PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium-browser \
NODE_ENV=production \
# Disable unnecessary features
CHROME_DEVEL_SANDBOX=/usr/lib/chromium/chrome-sandbox

WORKDIR /app

# Copy only necessary files from builder
COPY --from=builder /app/node_modules ./node_modules
COPY package*.json ./
COPY lighthouserc.js ./

# Run as non-root user for security
RUN addgroup -g 1001 -S nodejs && \
adduser -S lighthouse -u 1001 && \
chown -R lighthouse:nodejs /app

USER lighthouse

# Run Lighthouse CI tests
CMD ["npm", "test"]

Step 1: Build the Docker Image

docker build -t lighthouse-ci-test .

Expected output:

[+] Building 25.3s (15/15) FINISHED
=> exporting to image
=> => writing image sha256:c35cbd3762752cb...
=> => naming to docker.io/library/lighthouse-ci-test

Build time: ~20–30 seconds (first time)

Step 2: Run the Test

docker run --rm lighthouse-ci-test

What happens:

  1. Container starts
  2. Lighthouse runs 3 times on www.pradappandiyan.com
  3. Results are aggregated (median values)
  4. Report is generated and uploaded
  5. Container automatically removes itself (--rm)

Step 3: View Results

The test output shows:

✅  Configuration file found
✅ Chrome installation found
Healthcheck passed!
Running Lighthouse 3 time(s) on https://www.pradappandiyan.com
Run #1...done.
Run #2...done.
Run #3...done.
2 result(s) for https://www.pradappandiyan.com/ : ⚠️ categories.accessibility warning for minScore assertion
expected: >=0.8
found: 0.75
⚠️ largest-contentful-paint warning for maxNumericValue assertion
expected: <=2500
found: 4039.77
Open the report at https://storage.googleapis.com/...

Using NPM Scripts (Easier)

We’ve configured convenient npm scripts:

# Build the image
npm run docker:build

# Run the test
npm run docker:run

Advanced Usage

Save Results Locally

Mount a volume to persist results:

docker run --rm \
-v $(pwd)/.lighthouseci:/app/.lighthouseci \
lighthouse-ci-test

Results are saved to .lighthouseci/ directory.

Custom Configuration

Edit lighthouserc.js to change test parameters:

module.exports = {
ci: {
collect: {
url: ['https://www.pradappandiyan.com'],
numberOfRuns: 3, // Change number of runs
settings: {
chromeFlags: '--no-sandbox --disable-dev-shm-usage --headless',
preset: 'desktop', // or 'mobile'
},
},
assert: {
assertions: {
'categories:performance': ['error', { minScore: 0.7 }],
'largest-contentful-paint': ['warn', { maxNumericValue: 2500 }],
// Add more assertions
},
},
},
};

Test Multiple URLs

# Modify lighthouserc.js
url: [
'https://www.pradappandiyan.com',
'https://www.pradappandiyan.com/about',
'https://www.pradappandiyan.com/contact'
]

Run in CI/CD

GitHub Actions Example:

name: Lighthouse CI
on: [push, pull_request]
jobs:
lighthouse:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3

- name: Build Lighthouse Docker
run: docker build -t lighthouse-ci-test .

- name: Run Lighthouse Tests
run: docker run --rm lighthouse-ci-test

GitLab CI Example:

lighthouse-test:
image: docker:latest
services:
- docker:dind
script:
- docker build -t lighthouse-ci-test .
- docker run --rm lighthouse-ci-test
only:
- main
- merge_requests

Understanding the Test Results

Performance Metrics Tested

MetricThresholdWhat It MeasuresPerformance Score≥70%Overall page performanceFirst Contentful Paint (FCP)<2000msTime to first visible contentLargest Contentful Paint (LCP)<2500msTime to main content loadTime to Interactive (TTI)❤500msWhen page becomes fully interactiveSpeed Index❤000msHow quickly content is visually displayedTotal Blocking Time (TBT)❤00msTime page is blocked from user inputCumulative Layout Shift (CLS)<0.1Visual stability (no jumping content)

Other Categories

  • Accessibility: ≥80% — WCAG compliance
  • Best Practices: ≥80% — Security and modern standards
  • SEO: ≥80% — Search engine optimization

Reading the Report

Click the generated report URL to see:

  • 📊 Detailed performance metrics
  • 🎯 Specific recommendations
  • 📈 Performance timeline
  • 🔍 Network analysis
  • 📱 Mobile vs Desktop comparison

Troubleshooting

Docker Build Fails

# Clear Docker cache and rebuild
docker system prune -a
docker build --no-cache -t lighthouse-ci-test .

Out of Memory Errors

Increase Docker memory limit:

  • Docker Desktop → Settings → Resources → Memory → 4GB

Network Timeout

Website might be slow or unreachable:

// Increase timeout in lighthouserc.js
settings: {
maxWaitForLoad: 60000, // 60 seconds
}

Permission Denied

Ensure Docker daemon is running:

sudo systemctl start docker  # Linux
# Or restart Docker Desktop (Mac/Windows)

Performance Tips

Speed Up Builds

  1. Layer Caching: Don’t change package.json unless necessary
  2. Use Docker BuildKit:
DOCKER_BUILDKIT=1 docker build -t lighthouse-ci-test .
  1. Pre-pull Base Image:
docker pull node:18-alpine

Reduce Test Time

// Run fewer iterations
numberOfRuns: 1, // Instead of 3

// Test only performance
categories: ['performance']

Technical Deep Dive

Why Alpine Linux?

Alpine uses musl libc instead of glibc, and BusyBox instead of GNU utilities:

Size Comparison:
- Alpine: 5MB base
- Debian Slim: 124MB base
- Ubuntu: 77MB base

Trade-off: Some compatibility issues with native modules (rare)

Chromium vs Chrome

We use Chromium from Alpine repos:

  • ✅ Optimized for Alpine
  • ✅ Regularly updated
  • ✅ Smaller than Google Chrome
  • ✅ Same rendering engine (Blink)

Multi-Stage Build Efficiency

Traditional Build:     Multi-Stage Build:
┌─────────────┐ ┌─────────────┐
│ Build │ │ Build │ ← Discarded
│ Tools │ │ Tools │
│ npm cache │ │ npm cache │
│ Source │ └─────────────┘
│ Runtime │ ↓
└─────────────┘ ┌─────────────┐
~1.5GB │ Runtime │
│ Only │
└─────────────┘
~913MB

Comparison: Could We Go Smaller?

Option 1: Remote Browser (50MB)

❌ Requires external Chrome service ❌ Network latency affects results ❌ Additional infrastructure

Option 2: Lighthouse API (10MB)

❌ Depends on Google’s servers ❌ Rate limiting ❌ No custom configurations

Option 3: Our Solution (913MB)

✅ Self-contained ✅ Consistent results ✅ Full control ✅ No external dependencies ✅ Best balance of size and functionality

Conclusion

Our Lighthouse CI Docker setup achieves an optimal balance:

  • Small enough: ~913MB is lightweight for browser testing
  • Fast enough: Builds in 20–30 seconds
  • Complete enough: Full Lighthouse capabilities
  • Secure enough: Non-root user, minimal attack surface
  • Production-ready: Used in CI/CD pipelines

The ~650MB Chromium requirement is unavoidable for real browser testing. Any significantly smaller solution would sacrifice functionality or require external dependencies.

Resources

Built with ❤️ for performance testing

I have created a project on GitHub and added the code here.

Feel free to hit clap if you like the content. Happy Automation Testing :) Cheers. 👏 (www.pradappandiyan.com)

If you’d like to support my work, please leave a good rating for my Chrome plugin here and my Firefox plugin here.

I have created a test automation framework for API and UI for the QA community. If you found this helpful, leave a ⭐ on GitHub or try contributing to any feature.

--

--

Pradap Pandiyan
Pradap Pandiyan

Written by Pradap Pandiyan

I’m a passionate QA Engineer. I’m a motovlogger, content creator, off-roader and freelancer. Buy me a coffee here https://www.buymeacoffee.com/pradappandiyan

No responses yet