ShadowArcanist/beacons

A lightweight, self-hosted, cookieless web analytics and link shortener.

★ 2Forks 0TypeScriptGitHub ↗Compare

README

Beacon

A lightweight, self-hosted, cookieless web analytics and link shortener. One small Rust binary, a single SQLite file, and about 11 MB of RAM.

Important

This project was created entirely with AI, but the application has been thoroughly tested.

It was built primarily for my personal use, so I will not be merging pull requests or adding features unless I need them myself. If you want changes, feel free to fork this repository.

Why?

I had used Umami Analytics for about two years. It does the job well, but over time Umami kept adding features I don't need, and I kept running it anyway.

Recently my VPS memory sat around 91% (of 4 GB), so I looked for what I could cut to free some up and found that Umami, together with its PostgreSQL database, was quietly using around 480 MB:

So I decided to build my own analytics with only the features I need, and keep it light on the VPS. Beacon currently uses about 11 MB of memory on the same server:

Features

  • Cookieless and privacy-friendly — no cookies, no consent banner, no raw IP stored. Visitors are counted with a pseudonymous daily identifier.
  • Core Web Vitals — real-user LCP, INP, CLS, FCP and TTFB with p50/p75/p95 on a dedicated Performance page.
  • Link shortener — create short links on your own domains, each with its own analytics.
  • Shareable public dashboards — per site and per link, with selectable sections and a minimum-count threshold so small numbers stay private.
  • The usual reports — pages, referrers, UTM sources, countries, devices, browsers, operating systems, plus a realtime view.
  • GeoIP — country, region and city from the bundled DB-IP Lite database.
  • Multiple sites and custom domains, multi-user with owner/admin/viewer roles.
  • Bot filtering and IP-based rate limiting on the public endpoints.
  • A public read API for pulling aggregate stats into your other apps (see below).

Limitations

  • Single-node and SQLite-backed. Rate limits are per-instance and in-memory; it is not designed for multi-replica horizontal scaling.
  • Because there are no cookies, returning-visitor identity resets daily by design.
  • The public API is aggregate-only and requires enabling public sharing per site.
  • No plugin system — it is built primarily for the author's own use.

How to Deploy

Beacon exposes three ports:

Port Purpose
3001 Admin dashboard and API (published on host loopback by default)
3002 Public origin: tracker script (/data.js), event ingestion (/api/receive), and shared public pages
3003 Short-link redirects

Put a reverse proxy (Traefik, Caddy, Nginx, Cloudflare…) in front, routing your admin hostname to 3001 and your public analytics hostname to 3002.

Deploy with Docker Compose
  1. Copy compose.yaml from this repo to your server.
  2. Set the bootstrap credentials (they create the owner account on first start; the password must be 12–256 characters):
    export [email protected]
    export BEACON_ADMIN_PASSWORD='a-long-random-password'
  3. Start it:
    docker compose up -d
  4. The admin dashboard is published on 127.0.0.1:3001. Reach it through your reverse proxy, or open http://localhost:3001 on the host. Change your password from Settings → Account after the first login.
Deploy with Coolify
  1. Add a new resource → Docker Compose Empty.

  2. Paste the contents of coolify.yaml from this repo.

  3. Set BEACON_ADMIN_EMAIL and BEACON_ADMIN_PASSWORD in the environment.

  4. Open the Domains page and add two domains, each pointing to a container port:

    • Admin dashboard → https://admin.example.com:3001
    • Public analytics (tracker + shared pages) → https://analytics.example.com:3002

    (Add a third for :3003 only if you use short links.)

  5. Set BEACON_PUBLIC_URL to your public analytics domain (e.g. https://analytics.example.com).

  6. Click Deploy.

Configuration

Set these as environment variables.

Variable Default Description
BEACON_ADMIN_EMAIL — Owner account email, created on first start only.
BEACON_ADMIN_PASSWORD — Owner password (12–256 chars), first start only. Change it after logging in.
BEACON_PUBLIC_URL — Public origin (the collector port), e.g. https://analytics.example.com. Required, or the tracker snippet and public pages are disabled.
BEACON_PROXY_HOPS 0 Trusted reverse proxies in front: 0 direct, 1 Traefik, 2 Cloudflare + Traefik.
BEACON_ADMIN_BIND 127.0.0.1 Host interface the admin port is published on (Docker Compose). Set to 0.0.0.0 to reach it directly from the network.
BEACON_ADMIN_PORT 3001 Admin dashboard and API.
BEACON_COLLECTOR_PORT 3002 Tracker ingestion and public pages.
BEACON_REDIRECT_PORT 3003 Short-link redirects.
BEACON_SECURE_COOKIES true Secure flag on session cookies. Browsers drop them over plain HTTP (except localhost).
BEACON_RETENTION_DAYS 0 Delete raw events older than N days once a day (daily aggregates are kept). 0 keeps everything.
BEACON_SESSION_HOURS 168 Admin session lifetime in hours.
RUST_LOG server=info,tower_http=info Log level.

Add the tracker to your site

Copy the exact snippet from Site settings → Tracker. It looks like:

<script defer src="https://analytics.example.com/data.js" data-site="YOUR_SITE_PUBLIC_ID"></script>

Public API

When a site (or link) has public sharing enabled, other apps can read its aggregate stats without authentication. All requests are served from your public origin (the collector port) and are rate limited.

GET /api/public/sites/{public_slug}/overview?from=<unix_seconds>&to=<unix_seconds>
GET /api/public/sites/{public_slug}/{dimension}       # path | referrer | country | browser | os | device
GET /api/public/links/{public_slug}/overview
GET /api/public/links/{public_slug}/{dimension}

from/to are optional (default last 30 days). Only the sections you enabled for the page are returned.

Credits

IP geolocation by DB-IP.

License

MIT

Contributors

ShadowArcanist

Issues