reportd

May 7, 2026 ยท View on GitHub

GoDoc Go Report Card

A self-hosted service for collecting browser security reports and Web Vitals, with a dashboard for understanding your site's health.

What it does

reportd receives data from two browser APIs and presents it in a dashboard:

  • Web Vitals (LCP, CLS, INP, FCP, TTFB) -- performance metrics sent by the web-vitals library
  • Browser Reports (CSP violations, deprecation warnings, interventions, crashes, COOP/COEP violations, permissions policy violations) -- sent automatically by browsers via the Reporting API

Data is stored in a SQL database (Postgres or SQLite, for fast dashboard queries via GORM) and BigQuery (for long-term analytics).

Supported report types

TypeSourceDescription
CSP violationreport-to / Reporting APIContent Security Policy violations
Expect-CTreport-toCertificate Transparency violations (legacy)
DeprecationReporting APIUsage of deprecated browser APIs
Permissions PolicyReporting APIBlocked access to browser features (camera, geolocation, etc.)
InterventionReporting APIBrowser blocked something for performance/UX
CrashReporting APITab crash (OOM) or unresponsive page
COEPReporting APICross-Origin-Embedder-Policy violations
COOPReporting APICross-Origin-Opener-Policy violations
Document PolicyReporting APIDocument-Policy violations

Unknown report types are stored as raw JSON for forward compatibility.

Setup

Configuration

reportd is configured via environment variables (prefix REPORTD_) or command-line flags:

VariableFlagRequiredDescription
REPORTD_DATABASE_URL--database_urlYesDatabase connection string (Postgres or SQLite)
REPORTD_PROJECT--projectYesGCP project ID for BigQuery
REPORTD_DATASET--datasetYesBigQuery dataset name
REPORTD_ANALYTICS_TABLE--analytics_tableYesBigQuery table for Web Vitals
REPORTD_REPORTS_TABLE--reports_tableYesBigQuery table for Report-To data
REPORTD_REPORTS_V2_TABLE--reports_v2_tableNoBigQuery table for Reporting API v1 data
PORT--NoHTTP port (default: 8080)

Docker

docker run -p 8080:8080 \
  -e REPORTD_DATABASE_URL=postgres://user:pass@host/reportd \
  -e REPORTD_PROJECT=my-gcp-project \
  -e REPORTD_DATASET=reporting \
  -e REPORTD_ANALYTICS_TABLE=analytics \
  -e REPORTD_REPORTS_TABLE=reports \
  -e REPORTD_REPORTS_V2_TABLE=reports_v2 \
  -v /path/to/gcp-service-account.json:/creds.json:ro \
  -e GOOGLE_APPLICATION_CREDENTIALS=/creds.json \
  ghcr.io/icco/reportd

Docker Compose (self-hosting)

The example below runs reportd with its own Postgres database in a single docker compose up -d. BigQuery is required for long-term analytics, so you'll also need a GCP project, a dataset, three tables (analytics, reports, reporting), and a service account JSON key with write access. For SQLite-only usage (no Postgres container), see the note below.

Save this as docker-compose.yml:

services:
  reportd:
    image: ghcr.io/icco/reportd:main
    restart: unless-stopped
    ports:
      - "8080:8080"
    environment:
      REPORTD_DATABASE_URL: postgresql://reportd:reportd@db:5432/reportd?sslmode=disable
      REPORTD_PROJECT: my-gcp-project
      REPORTD_DATASET: reportd
      REPORTD_ANALYTICS_TABLE: analytics
      REPORTD_REPORTS_TABLE: reports
      REPORTD_REPORTS_V2_TABLE: reporting
      GOOGLE_APPLICATION_CREDENTIALS: /creds.json
    volumes:
      - ./gcp-service-account.json:/creds.json:ro
    depends_on:
      - db

  db:
    image: postgres:17
    restart: unless-stopped
    environment:
      POSTGRES_USER: reportd
      POSTGRES_PASSWORD: reportd
      POSTGRES_DB: reportd
    volumes:
      - reportd-pgdata:/var/lib/postgresql/data

volumes:
  reportd-pgdata:

Then put your GCP service account key next to it as gcp-service-account.json and run:

docker compose up -d

reportd will be reachable at http://localhost:8080. Put it behind a reverse proxy (Caddy, nginx, Traefik) to expose it on a public hostname over HTTPS โ€” browser reporting endpoints must be HTTPS.

To use SQLite instead of Postgres, drop the db service and depends_on, set REPORTD_DATABASE_URL: sqlite:///data/reportd.db, and mount a volume at /data for persistence.

Local development

# Start Postgres
docker run -d --name reportd-pg -p 5432:5432 \
  -e POSTGRES_PASSWORD=postgres -e POSTGRES_DB=reportd \
  postgres:17

# Run reportd
export REPORTD_DATABASE_URL=postgres://postgres:postgres@localhost/reportd
export REPORTD_PROJECT=my-project
export REPORTD_DATASET=my-dataset
export REPORTD_ANALYTICS_TABLE=analytics
export REPORTD_REPORTS_TABLE=reports
export REPORTD_REPORTS_V2_TABLE=reports_v2
go run main.go

For SQLite instead of Postgres:

export REPORTD_DATABASE_URL=sqlite:///tmp/reportd.db

Integrating with your site

Web Vitals

Add this snippet to your site to send Web Vitals to reportd:

<script type="module">
  import { onCLS, onINP, onLCP, onFCP, onTTFB } from 'https://unpkg.com/web-vitals@5?module';

  function sendToAnalytics(metric) {
    const body = JSON.stringify(metric);
    (navigator.sendBeacon && navigator.sendBeacon('https://your-reportd-instance/analytics/yoursite', body)) ||
      fetch('https://your-reportd-instance/analytics/yoursite', { body, method: 'POST', keepalive: true });
  }

  onCLS(sendToAnalytics);
  onFCP(sendToAnalytics);
  onINP(sendToAnalytics);
  onLCP(sendToAnalytics);
  onTTFB(sendToAnalytics);
</script>

Browser reports

Add these HTTP headers to your site's responses:

Reporting-Endpoints: default="https://your-reportd-instance/reporting/yoursite"
Content-Security-Policy: ...; report-to default;
Cross-Origin-Opener-Policy: same-origin; report-to="default"
Cross-Origin-Embedder-Policy: require-corp; report-to="default"
Permissions-Policy: camera=(), geolocation=(); report-to="default"

For legacy Report-To support:

Report-To: {"group":"default","max_age":10886400,"endpoints":[{"url":"https://your-reportd-instance/report/yoursite"}]}

API reference

Ingestion (POST)

EndpointContent-TypeDescription
POST /analytics/{service}application/jsonWeb Vitals data
POST /report/{service}application/csp-report, application/expect-ct-report+json, application/reports+jsonLegacy Report-To data
POST /reporting/{service}application/reports+jsonReporting API v1 data

Dashboard (GET)

EndpointDescription
GET /Service index with health indicators
GET /view/{service}Dashboard for a specific service
GET /api/vitals/{service}JSON: p75 summaries and daily time series
GET /api/reports/{service}JSON: report counts, recent reports, top violated directives
GET /analytics/{service}JSON: daily average Web Vitals
GET /reports/{service}JSON: daily report counts
GET /servicesJSON: list of all services
GET /healthzHealth check

Dashboard features

The service view page provides:

  • Core Web Vitals cards with p75 values rated against Google's thresholds (good / needs improvement / poor)
  • Time-series charts for each metric with threshold bands
  • Report volume chart showing report counts by type over time
  • Recent CSP violations table with violated directive, blocked URI, document URI, and source location
  • Recent reports table for deprecation warnings, interventions, crashes, and other browser reports
  • Top violated directives bar chart showing the most frequently violated CSP directives