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.
- Browser-side: doable-tracker (JS) —
~3.4 KB gzipped, consent-aware,doable('revoke')to opt out. - Server-side: doable-sdk (Python) — batched, backend-friendly.
- Retention you control: access logs 90 d, daily analytics 2 y, consent logs 7 y (all config vars).
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.
- Runs nightly at 04:00 (
doable-ai-insightsPM2 process). - History preserved per
(source, timeframe)for 365 d. - Owner emails (English + Swedish) fire when traffic drops <70 %, spikes
200 %, error rate crosses 1 %→5 %, P95 doubles past 500 ms, or a new 100-hit AI bot / scraper appears.
- Stale-finding suppression (v2.13.11): persistent conditions (same dead route, same chronically slow path, same recurring bot) email once and then go quiet until the situation changes — severity escalation or a configurable per-type metric shift wakes them up again. Cut repeat alerts by ~59 % in production backtest.
- Per-user opt-out at
/dashboard/settings. - Data never leaves the server — no cloud LLM calls.
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.
- Clicks / impressions / CTR / avg. position KPIs, top queries, top pages, device + country breakdowns.
- Sync runs at 04:00 (
doable-gsc-syncPM2 process, isolated from AI Insights so one failure doesn't starve the other). - After 3 consecutive failures, the integration flips to
error, the dashboard flags it, and adoable_gsc_sync_failedemail goes to owners.
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.
- Sync runs Mondays 02:00 (
doable-psi-syncPM2 process, staggered from the 04:00 AI Insights + GSC slot). /admin/psilists every audited URL with Google-style colour-coded badges (≥ 90 green, 50–89 amber, < 50 red).- Per-source detail view shows current scores, Core Web Vitals (FCP / LCP / CLS / TBT), top opportunities by ms savings, and a 12-week history table. Re-audit button kicks off an ad-hoc run.
- Same 3-strikes failure pattern as GSC;
doable_psi_sync_failedemail at the threshold. - Set
PSI_API_KEY(Google Cloud project) in.envto enable; feature ships dark until the key is configured.
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:
- Add analytics to an existing external site → Quickstart § 1
- Host a site through doable-server → Quickstart § 2
- Connect Google Search Console → Quickstart § 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):
- Outage alerter (v2.13.9,
outage-alertPM2 process) — every 5 min, computes per-route 5xx rate over a 15-min trailing window fromaccess_logsand emails the operator when a route crossesOUTAGE_ALERT_THRESHOLD_PCT(default 20 %) with at leastOUTAGE_ALERT_MIN_REQUESTS(default 50) samples. Per-route cooldown viaactivity_logsprevents repeat email during a sustained outage. Closed a silent multi-site 5xx storm that would otherwise have gone unnoticed for hours. - CR-029 hot-standby + CR-030 admin visibility — bare-metal gateway
pushes Apache configs /
.env/ SQLite DB / Letsencrypt to a parallel LXC standby every 5–60 min. The/admin/gatewayspage surfaces the sync timer state, DB drift, and reachability.
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)
- User-facing self-service for GSC and AI Insights — today the GSC connect wizard is admin-only; bringing it to the user dashboard closes the "one-email setup, no OAuth" promise for non-admin site owners.
- Per-resource AI Insights toggle — enable/disable nightly analysis on specific routes or tracking projects, not just globally.
- Interactive onboarding tour — a guided tour on top of the existing first-run checklist.
- Branded email design — unified branding across all owner-alert emails (insights, GSC failures, consent confirmations).
- A11y audit — dedicated review with testing.
Where to go next
- Quickstart — three onboarding paths, each ≤ 5 steps.
- GDPR compliance notes — what we store, how long, and what your visitors can request.
- About — the product thesis, in one page.