← Back to home

doable-server — Features Overview

A privacy-first analytics and SEO gateway for self-hosted sites.

doable-server is one binary that does three things well: it serves your sites as a reverse proxy, it measures the traffic respectfully, and it tells you what the numbers mean. Cookie consent, IP minimization, nightly AI analysis, and Google Search Console all live in the same dashboard.

Last updated for v2.14.0.


Three pillars

1. Analytics — respectful by default

Server-side analytics from the proxy plus an optional 5 KB JS tracker. Built-in GDPR consent banner, proxy IPs pseudonymized to /24 by default and zero-fetched when consent is declined. DNT: 1 is honored as a fallback.

UTM attribution + aggregation endpoint (v2.11.0): GET /api/v1/analytics/utm-aggregate returns count / unique / by-day / top-sources filtered by tracking_id, event, utm_source, utm_medium, utm_campaign, and date range. Last-non-direct-click attribution matches the industry default (GA4, Matomo, Plausible, Mixpanel). A joint source|event counter lives in the daily aggregate — so you can answer "how many form submissions came from retailer code X last month?" at any point within the 2-year retention window, not just the 7-day raw-event window.

Reserved event-name vocabulary (documented convention, no enforcement): page.view, page.exit, cta.click, outbound.click, form.view, form.submit. Custom event names allowed beyond these.

Built-in engagement signals (v2.13.8): the tracker now fires scroll (depth 25 / 50 / 75 / 100 %), outbound_click, and time_on_page out of the box. Each event-name has its own daily counter in external_analytics_daily.event_counts and shows up on the admin tracking-project page next to top pages and referrers, so you can answer "are visitors actually reading?" without writing SQL. pageviews reflects event_name == 'page.view' only — engagement events do not inflate it.

PII filtering at ingestion: the tracker rejects event payload keys matching known PII patterns (email, phone, mobile, address, ssn, personnummer, password, token, etc.) at both the browser layer and the server. form.submit events carry attribution metadata only — form content belongs in the adopter's own backend, not in doable-tracker events.

Full API reference — endpoints, auth, rate limits, event vocabulary, examples.

2. AI Insights — on your own hardware

Daily Ollama-powered analysis, cache-first dashboard reads, deterministic significant-finding alerts to route/project owners.

3. Search — native GSC integration

Google Search Console data daily, right next to your traffic. No OAuth dance — users add one service-account email to their GSC property.

4. Performance — actionable Lighthouse audits (v2.14.0)

Weekly PageSpeed Insights sweep per routable site — performance, SEO, accessibility, best-practices scores plus categorised opportunities (render-blocking resources, unused JavaScript, properly- sized images, etc.) that translate directly to a developer to-do list.


What makes it different

Capability doable-server GA4 Matomo Plausible Ahrefs
Self-hosted ◑ (hosted default)
Built-in consent banner ❌ (needs CMP) ◑ (plugin) ◑ (cookie-less)
Privacy-minimized proxy logs ✅ IP truncation, DNT honored ◑ (configurable)
AI-powered insights ✅ local Ollama ◑ cloud-only
Google Search Console integration ✅ service-account ✅ (via Looker) ◑ (plugin) ◑ (hosted) ✅ (SEO tool)
Reverse proxy / site hosting
Tracker size 5 KB ~50 KB ~25 KB 1 KB
Raw event data stays on your box depends

Ahrefs and SEMrush are SEO tools (not analytics); they are complementary rather than alternatives. The comparison above is accurate as of v2.13.12 and will drift — treat it as a point-in-time snapshot, not a commitment.


Deployment modes

Tracker-only — for any existing site

Create a tracking project, copy the embed snippet, drop it into your site. Ideal when doable-server doesn't proxy your traffic but you still want the dashboard, consent banner, AI analysis, and GSC in one place.

See Quickstart — Add analytics to an existing site.

Full proxy mode — for managed sites

Add a route, configure Apache, point DNS. Visitor traffic is proxied through doable-server and logged server-side; the JS tracker is optional. Gives you server-side IP pseudonymization, per-route auth, JWT injection, path blocking, and health monitoring.

See Quickstart — Host a site through doable-server.

GSC attach

Works on either mode. One email-paste per property, no OAuth.

See Quickstart — Connect Google Search Console.


Getting started

Three paths, each five steps or fewer:

  1. Add analytics to an existing external siteQuickstart § 1
  2. Host a site through doable-serverQuickstart § 2
  3. Connect Google Search ConsoleQuickstart § 3

Once you're in, the admin Integrations overview is the single place to see which resources have which capabilities enabled and spot anything that needs attention.


Operational health

doable-server also watches itself. Two scheduled jobs surface problems to the operator without external dependencies (no Sentry / Grafana / PagerDuty needed):

Both are env-driven, fail-safe (the gateway doesn't depend on them to serve traffic), and reusable building blocks rather than a monolithic ops layer.


Privacy posture

Concern Default Opt-out / override
Proxy IP logging Truncated to /24 PROXY_IP_TRUNCATE=false
Visitor consent (browser) Banner shown if the tracking project requires it doable('revoke') from JS
Visitor consent (proxy log) Declined / DNT → no detailed logging Route.require_consent_proxy column
AI analysis Runs locally, data stays on the server AI_INSIGHTS_ENABLED=false or per-user opt-out
GSC data Aggregate only (no per-visitor info) Disconnect removes data after 30 d retention
Retention — access logs 90 days ACCESS_LOG_RETENTION_DAYS
Retention — daily analytics 730 days ANALYTICS_RETENTION_DAYS
Retention — consent logs 2555 days (7 y, audit trail) CONSENT_LOG_RETENTION_DAYS
Retention — AI insight runs 365 days AI_INSIGHTS_RETENTION_DAYS
Retention — GSC daily tables 730 days GSC_RETENTION_DAYS

See the GDPR compliance notes for the full privacy story, including the subject-access-request flow.


Roadmap (directional, not committed)


Where to go next