Skip to content

feat: add opt-in anonymous telemetry and internal tracker dashboard - #21

Merged
DeepZone merged 7 commits into
mainfrom
feat/anonymous-telemetry
Aug 21, 2026
Merged

feat: add opt-in anonymous telemetry and internal tracker dashboard#21
DeepZone merged 7 commits into
mainfrom
feat/anonymous-telemetry

Conversation

@DeepZone

Copy link
Copy Markdown
Owner

Summary

Adds explicit opt-in, privacy-minimizing anonymous usage telemetry to Container Pilot and a separately deployable PostgreSQL tracker with an authenticated internal statistics dashboard. Telemetry remains disabled for all existing and new installations until an administrator enables it.

Architecture

  • Container Pilot builds one allow-listed aggregate schema-v1 payload for both preview and transmission.
  • Automatic delivery is limited to one attempt per 24 hours, delayed by a randomized 2–15 minute startup jitter, and bounded by an 8-second timeout.
  • cp-track.noisens.de terminates HTTPS and proxies only to public listener :3090.
  • The dashboard runs on independent listener :3091, intended for localhost or a private management LAN only.
  • PostgreSQL 16 is internal to Compose and has no published port.

Privacy model

Opt-in instead of opt-out; transparent instead of hidden; aggregated instead of detailed; minimal instead of curious. Tracker failures are fail-open and cannot block scans, updates, rollbacks, UI actions, or process startup. Remote addresses exist only briefly in in-memory rate-limit maps and are never stored or logged.

Collected data

  • Random UUID v4 installation identity and SHA-256 deletion-token hash
  • Container Pilot version/channel
  • Normalized architecture, Docker/API version, general OS, kernel major/minor
  • Aggregate total/running/stopped/healthcheck/automatic-policy counts
  • Boolean Watchtower migration, native HTTPS, private registry, and webhook adoption
  • Boolean Docker Hub/GHCR/GitLab/generic OCI categories
  • Cumulative successful/failed update and automatic/manual rollback counters

Explicitly excluded data

No hostnames, Docker host names, IP/MAC addresses, machine IDs, hardware serials, container names/IDs, image names/tags/digests, repositories, registry domains/URLs, labels, Compose metadata, networks, volumes, mount paths, ports, environment variables, usernames, credentials, tokens, secrets, certificates, browser data, application data, or file contents.

Security review confirmed:

  • Container/image/registry identities cannot enter the allow-listed payload builder.
  • Docker Name is never read into telemetry.
  • Remote IPs are not persisted or logged.
  • Error handlers never log request bodies.
  • Public listener has no dashboard/statistics routes (isolation tests cover /dashboard, /admin, and /api/dashboard/summary).
  • PostgreSQL has no host port.
  • secret directories are excluded from Git and Docker build contexts.
  • delete-token hashes are not returned by dashboard APIs.

Public endpoint

  • POST https://cp-track.noisens.de/api/v1/telemetry
  • DELETE https://cp-track.noisens.de/api/v1/telemetry/:installation_id
  • GET https://cp-track.noisens.de/healthz

All other public routes return 404. nginx and Caddy allow-list examples are documented.

Internal dashboard architecture

The internal listener requires login and provides 24h/7d/30d activity KPIs, version/architecture/Docker/OS distributions, container and feature adoption, registry categories, update/rollback statistics, 7/30/90-day time series, a shortened-ID installation list, details, and report history. Assets are local with no CDN, analytics, fonts, or external requests.

Database model

Versioned migration 001_initial.sql creates installations summaries and cascading reports history. Each report transactionally inserts history and upserts current cumulative values, avoiding double counting. Raw reports default to 90-day retention.

Security

Strict unknown-field rejection, bounded strings/counters, UUID v4 validation, 16 KiB body limit, per-installation and ephemeral IP rate limits, prepared queries, transactional migrations, login rate limiting, expiring server-side sessions, HttpOnly/SameSite cookies, optional Secure cookies, origin/CSRF checks, and browser security headers.

Tests

  • Existing Container Pilot tests pass.
  • Client tests cover default-off, identity persistence/reset, payload privacy, preview/transmission equality, fail-open errors, HTTPS policy, and counters.
  • Tracker tests cover schema rejection, bounds, body limit, deletion authentication, sessions/login limits, and public dashboard isolation.
  • PostgreSQL CI integration sends two client-built reports through the collector, verifies one installation/two reports/latest counter 24/delta 4, dashboard aggregation, and cascading authenticated deletion.
  • CI now syntax-checks/audits/tests/builds both AMD64 and ARM64 images for Container Pilot and the tracker.

Deployment

tracker/compose.yml, Dockerfile, file-based secret wiring, healthchecks, migrations, backup/restore, update, retention, nginx, Caddy, hardening, and troubleshooting are documented in tracker/README.md. Client behavior is documented in docs/telemetry.md.

Known limitations

  • The tracker dashboard is intentionally single-admin and configured through a file-based password secret.
  • In-memory rate-limit and dashboard session state resets on tracker restart; telemetry and reports remain in PostgreSQL.
  • This PR does not deploy cp-track.noisens.de; deployment remains an explicit infrastructure step.
  • No release, version bump, or tag is included; changes remain under Unreleased as required.

Comment thread tracker/src/public-api.js

const MAX_BODY = 16 * 1024;
const security = { 'cache-control':'no-store','content-type':'application/json; charset=utf-8','x-content-type-options':'nosniff','x-frame-options':'DENY','content-security-policy':"default-src 'none'; frame-ancestors 'none'; base-uri 'none'",'referrer-policy':'no-referrer' };
function json(res,status,value){const body=JSON.stringify(value);res.writeHead(status,{...security,'content-length':Buffer.byteLength(body)});res.end(body);}
Comment thread tracker/src/server.js
const MAX_BODY = 16 * 1024;

function headers(extra = {}) { return { 'cache-control': 'no-store', 'x-content-type-options': 'nosniff', 'x-frame-options': 'DENY', 'content-security-policy': "default-src 'self'; style-src 'self' 'unsafe-inline'; script-src 'self'; img-src 'self'; frame-ancestors 'none'; base-uri 'none'; form-action 'self'", 'permissions-policy': 'camera=(), microphone=(), geolocation=()', 'referrer-policy': 'no-referrer', ...extra }; }
function json(res, status, value, extra = {}) { const body = JSON.stringify(value); res.writeHead(status, headers({ 'content-type': 'application/json; charset=utf-8', 'content-length': Buffer.byteLength(body), ...extra })); res.end(body); }
@DeepZone
DeepZone marked this pull request as ready for review August 21, 2026 12:56
@DeepZone
DeepZone merged commit 66e2228 into main Aug 21, 2026
12 checks passed
@DeepZone
DeepZone deleted the feat/anonymous-telemetry branch August 21, 2026 12:56
DeepZone added a commit that referenced this pull request Aug 21, 2026
)

* feat: add telemetry state and payload builder

* feat: add telemetry settings UI

* feat: add telemetry collector and internal dashboard

* test: add telemetry privacy and integration coverage

* docs: document anonymous telemetry

* fix: use explicit telemetry timeline aliases

* release: prepare Container Pilot v0.9.0-rc.11

---------

Co-authored-by: Norman Sens <n.sens@noisens.de>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants