Operator manual
Run Thanos: monitor everything, know first, fix fast.
Thanos watches your endpoints, servers, and Kubernetes clusters, turns failures into a single clean incident, tells you why, and can run the fix itself. This manual walks every feature end to end, with the exact steps to set it up.
Overview & architecture
Thanos is a full-stack monitoring platform. You point it at things you care about (a URL, a host, a cluster), it checks them on a schedule, and when something breaks it opens an incident, notifies the right people, and optionally runs a runbook to remediate.
Control plane
Go/Gin API + Postgres/TimescaleDB. Serves the UI, runs the detection engines, stores time-series heartbeats.
Agents
Lightweight Go binaries you run near your infrastructure to check endpoints from a region and collect server / eBPF metrics.
Web app
React UI where you configure everything and read incidents, dashboards, and status pages.
Two ways a check runs: central (the backend probes the target directly) or agent (a remote agent probes it from its region). Background engines run continuously on a leader-elected replica: the watchdog, central checker, anomaly engine, escalation engine, and SLO-burn engine.
Terminology. An endpoint is a URL/host you monitor. A server is a machine (SSH/agent) you collect metrics from. An incident is one open problem. A runbook is a sequence of scripts that can run to fix it.
Deploy the platform
Docker Compose is the supported path. You need Docker 24+ / Compose v2 and a TLS proxy in front for production.
- Prepare secrets. Copy the env templates and fill them:
deploy/backend.envanddeploy/postgres.env. Generate strong keys:
Production startup refuses to boot on weak or missing secrets.# 32-byte hex for encryption + agent pepper, 64-byte for JWT openssl rand -hex 16 # ENCRYPTION_KEY, AGENT_TOKEN_PEPPER openssl rand -hex 32 # JWT_SECRET - Bring the stack up.
Migrations apply automatically on backend startup.docker compose -f docker-compose.prod.yml up -d --build - Reach it. The frontend (UI +
/apiproxy) is onhttp://localhost:8088; the backend that agents connect to is onhttp://localhost:8090. - Serve under your domain. Front the frontend container with a TLS proxy (nginx/Caddy/traefik) and set both
CORS_ORIGINSandWEBSOCKET_ALLOWED_ORIGINSto your origin. In production the backend refuses to start with*or empty WebSocket origins.
Reverse proxy body size. The K8s resource sync posts multi-MB payloads. Set client_max_body_size 64m; on every nginx hop in front of /api/, or large clusters silently fail with 413.
Health endpoints: GET /api/v1/live (liveness), /api/v1/ready (readiness incl. dependencies), /api/v1/health (per-service detail), /api/v1/metrics (Prometheus).
Accounts & sign-in
- Create the first account. Open the app and choose Sign up. Enter name, email, and a password of at least 8 characters. You're signed in and land on the dashboard.
- Sign in later with email + password. Sessions persist across reloads.
- Google sign-in (optional). The Google button only appears when a client ID is configured, so an unconfigured deploy never shows a broken button. To enable it, see Configuration reference → Google sign-in.
Tenancy. Every resource is scoped to your account. You never see, and can never act on, another tenant's endpoints, servers, or incidents.
The dashboard
The landing page summarizes your world: counts of endpoints by status, open incidents, and the most recent activity. Use it as the morning glance; drill into Incidents or Endpoints for detail. Status is encoded as a pill everywhere: UP DEGRADED DOWN.
Endpoint monitoring
Monitors HTTP / HTTPS / TCP targets with configurable checks, SSL tracking, and multi-region rollup.
- Go to Endpoints → Add Endpoint.
- Enter a Name and the URL (e.g.
https://api.example.com/health). Pick the Protocol. - Set the Check interval (seconds between probes) and Timeout.
- Optional: set an Expected status code and/or Expected body substring, custom headers, request method and body, and accepted status codes (e.g.
2xx, 3xx). - Optional Authentication: none, basic, or bearer token — credentials are encrypted at rest.
- Choose the Check mode (see below) and, for agent mode, the agent that will run it.
- Save. The endpoint begins checking on its interval; its status appears within one cycle.
An endpoint that starts failing accrues consecutive failures; once past the threshold it flips DOWN and opens an incident. When it recovers, the incident resolves and a recovery notification is sent through the same channels.
Muting & maintenance
On an endpoint's edit page, the Alert Suppression card lets you mute all alerts, set a maintenance window (Muted until), or suppress specific incident types (e.g. only SSL_EXPIRY). Muted endpoints are still checked; they just don't dispatch alerts.
Multi-region & availability policy
Attach regional targets so several agents check the same endpoint from different regions. The Availability policy then decides the rolled-up status: any (one region UP → UP), all (every region must be UP), or quorum (at least K regions UP). A mix of UP and failing regions reads as DEGRADED.
Check modes: central vs agent
| Mode | Who probes | Use when |
|---|---|---|
| Central | The backend, from its own network | Public endpoints; the default. No agent needed. |
| Agent | A remote agent, from its region | Private endpoints, or to measure availability from specific geographies. |
Private targets. Endpoint URLs must resolve to a public address — the SSRF guard rejects loopback / RFC1918 / link-local / metadata targets. To monitor something private, use agent mode with an agent inside that network.
SSL / certificate tracking
For HTTPS endpoints, Thanos reads the certificate from the TLS handshake on every check and records the issuer and expiry. When the certificate nears expiry it opens an SSL_EXPIRY incident. Configure the warning windows per endpoint under Cert expiry thresholds (days), or fall back to the global default.
Works for central checks. Cert tracking runs for both agent and central-mode HTTPS endpoints, and the expiry is read from the handshake even when you enable Ignore TLS errors.
Latency & anomaly detection
Two independent ways to catch an endpoint that's up but slow.
Latency threshold (deterministic)
On the endpoint's edit page set a Latency threshold (ms). When sustained average latency crosses it, Thanos opens a DEGRADED incident — no baseline required, so it works on brand-new endpoints. Leave it empty to disable.
Anomaly detection (statistical)
Enable Anomaly detection to catch deviations from the endpoint's own learned baseline. It compares the current 5-minute bucket to a seasonal (hour-of-day) baseline and fires when the z-score exceeds your threshold for two consecutive ticks.
- Z-score threshold — standard deviations from the mean before alerting (default 3.0).
- Baseline window — hours of history used to learn normal (default 24).
Cold start. Anomaly detection needs ~24h of history before it can score. Use the deterministic latency threshold for immediate coverage, and let anomaly detection layer in once the baseline warms up.
Endpoint groups
Groups organize endpoints for status pages and SLOs. Create one under EP Groups, then assign a group to an endpoint from its edit page. A group is the unit an SLO target is measured over and a status page is built from.
Agents & regions
An agent runs near your infrastructure to check endpoints from a region and collect server metrics. Setting one up is two steps: register it, then run the binary with its token.
- Register the agent. Go to Agents → Add Agent, give it a name and one or more regions (e.g.
sgp,eu). Save. - Mint a token. Open the agent and create a token. Copy it now — it's shown once. Optionally scope it read-only or to specific CIDRs.
- Run the agent. On the host, run the binary (or container) pointed at your backend:
Or as a native binary (better for host-level server metrics):docker run -d --name thanos-agent \ -e AGENT_API_URL=https://your-domain \ -e AGENT_TOKEN=<token-from-ui> \ -e AGENT_REGION=sgp \ thanos-agentAGENT_API_URL=https://your-domain \ AGENT_TOKEN=<token> \ AGENT_REGION=sgp \ AGENT_STATE_DIR=/var/lib/thanos-agent \ ./thanos-agent - Confirm it's online. Within a minute the agent shows Online in Agents with a fresh heartbeat, IP, and version.
Assign agent-mode endpoints (and servers) to the agent; it fetches its work list, probes on schedule, and streams heartbeats back. The rebalancer spreads endpoints across healthy agents and can hand a region's work to a fallback agent if the primary dies.
Token capabilities. A token marked read-only may only issue GET requests; write routes return 403. Agent ingest is assignment-scoped — a token can only submit data for the servers and endpoints assigned to it.
eBPF service metrics
Optional. Capture RED metrics (request rate, errors, duration) plus TCP and resource-pressure metrics straight from the kernel — no code changes to the monitored apps.
A separate node-agent loads eBPF programs on a Linux host and exposes the collected metrics as Prometheus text. Your Thanos agent forward-scrapes that endpoint and ships the samples to the backend, where they're stored in a queryable time-series store.
- Run the node-agent on the host (needs a recent kernel with BTF and CAP_BPF/root):
It attaches probes and serves metrics at./thanos-node-agent --listen=127.0.0.1:19100/metrics— HTTP durations, TCP connects/retransmits, CPU/IO/memory pressure, and more. - Point a Thanos agent at it by adding two variables and restarting the agent:
The agent forward-scrapes on the interval and submits the exposition to the backend.AGENT_EBPF_METRICS_URL=http://127.0.0.1:19100/metrics AGENT_EBPF_INTERVAL=15s - Verify. Samples begin landing in the metric store; the container-level RED metrics become available for the service map and analysis.
Not on by default. eBPF collection is opt-in: it requires the node-agent running and AGENT_EBPF_METRICS_URL set on an agent. The kernel tracer needs a real Linux kernel (it can't run in every sandbox/CI).
Server monitoring
Track host CPU / memory / disk and up-down status over SSH or via an agent.
- Go to Servers → Add Server. Enter a name and hostname/IP.
- Provide access: an SSH username with a saved key or password (or an assigned agent). For a private host, route through a bastion.
- Pick a Health check method —
ssh,http,telnet, ornone. When you assign an agent, the form defaults the method to SSH so the server is actually monitored. - Save. Status flips to Online once the first check succeeds.
Resource metrics need a source. Up/Down and latency come from the health check, but CPU / memory / disk require a metrics source (node_exporter scraped via a Prometheus/SigNoz integration). Without one, the resource columns stay –.
Bulk import / export
Import many servers from CSV (username + password creds; connectivity is probed, capped at 500 rows, private-range SSRF-guarded). Export writes a CSV; SSH passwords are omitted unless you pass ?includeCredentials=true, and every cell is neutralized against spreadsheet formula injection.
SSH keys & bastions
SSH Keys are reusable private keys stored encrypted at rest (with optional passphrase). Reference a saved key from a server or bastion instead of pasting it each time; a key that's in use can't be deleted.
Bastion Hosts are jump hosts for reaching servers on private networks. Add a bastion once, then select it on any server that lives behind it — Thanos dials the two hops with host-key pinning on both.
Web terminal
Open an interactive SSH session to any server from the browser under Terminal. Sessions are authenticated, ownership-checked before dialing, and host-key pinned. Open multiple tabs; they persist in the session and surface a green SSH badge on the servers list. Run a saved script on a server: Thanos uploads it over SSH to /tmp and executes it, capturing the output.
Kubernetes
Discover cluster resources, visualize them as a graph, and get advisor findings.
- Go to Kubernetes → Add Cluster. Provide access — paste a kubeconfig, or client-certificate data, and the API server URL.
- Assign the cluster to an agent that can reach the API server, and enable Discovery.
- Save. The cluster shows Connected. On the next discovery tick the agent enumerates nodes, pods, deployments, services, ingresses and more, and syncs them back.
Resource graph
Open K8s Graph (or View Graph on a cluster) and pick a namespace to see an interactive node-link graph — ownerRef, Service-selector, Ingress-backend, and volume/PVC↔PV edges across the discovered kinds.
Advisor
K8s Advisor runs checks against a connected cluster and reports issues (or confirms all checks passed). Trigger a fresh analysis with Analyse Now.
Discovery is opt-in. A connected cluster with discovery disabled shows 0 nodes / never synced. Enable discovery on the cluster (and make sure it's assigned to a running agent) to populate the graph.
Incidents
An incident is one open problem. It opens automatically when detection crosses a threshold, or you can create one manually against an endpoint or server.
Lifecycle
- Open. Detection fires; the incident lists the endpoint, type, severity, and failing regions.
- Acknowledge. On the incident page, click Acknowledge to signal you're on it (stops escalation from advancing).
- Resolve. When fixed, click Mark resolved. Recovery from monitoring auto-resolves it and clears the dedup window.
Grouping & flapping
A burst of related failures is grouped under one parent incident so you get one alert, not a storm. An endpoint that rapidly flips up/down is classified FLAPPING, which suppresses per-transition noise; it auto-resolves after a stable cooldown.
Notification channels
Where alerts go. Set up a channel once, then route rules to it.
SMTP; the recipients field is a comma-separated list.
Slack
Incoming webhook URL for a channel.
PagerDuty
Events API v2 routing key.
Webhook
Any HTTPS endpoint; JSON payload.
Microsoft Teams
Incoming webhook URL.
Lark / Google Chat
Space webhook URL.
- Go to Channels → Add Channel, choose the type, and paste the webhook URL / credentials.
- Save, then click Test on the channel row — Thanos sends a sample alert through the real sender so you can confirm delivery before a real incident depends on it.
Outbound safety. Every user-supplied URL (webhooks, integrations, collectors) is validated against the SSRF guard at save and at send, defeating DNS-rebinding — so a channel can't be used to reach internal services.
Alert rules
A rule decides which channel an incident is dispatched to, and at what severity. Rules apply globally across your endpoints; an endpoint can suppress rules it doesn't want.
- Go to Alert Rules → Add Rule. Name it and choose the notification channel it routes to.
- Set the severity and any per-channel routing. Save.
A rule may only reference your own channels; the dispatcher additionally refuses to deliver through a channel owned by another tenant.
Escalation & on-call
Make sure an unacknowledged incident reaches someone — climbing a ladder of steps on a schedule.
Escalation policies
- Go to Escalation Policies → New Policy. Add ordered steps. Each step has a delay (minutes after the incident opened) and an action: notify a channel, a specific user, or the current on-call.
- Attach the policy to an endpoint (from the endpoint's edit page).
The engine polls open incidents every 60 seconds. Step 0 with delay 0 fires on the first poll after the incident opens; a step with delay N fires ~N minutes in — unless the incident is acknowledged first. A claim-before-send guard means each step fires exactly once, even across replicas.
On-call schedules
Under On-Call Schedules build a weekly rotation of shifts. An escalation step that targets on-call resolves the person currently on shift and notifies them. The calendar view shows the rotation at a glance.
Scripts
A script is reusable shell (or other) code you can run on the backend host or on a server over SSH, and link into runbook steps.
- Go to Scripts → Add Script. Name it, choose the language, paste the content, set a timeout.
- Run / Logs to execute it. In the Run modal choose the target — the backend host or a specific server (SSH) — and Thanos captures and persists the output.
Daemon-starting steps. A script that launches a long-running process over SSH will hang the session until timeout. Fully detach it — e.g. ( setsid mycmd </dev/null >/var/log/x.log 2>&1 & ) — so the step returns cleanly.
Runbooks
A runbook is an ordered list of steps (each backed by a script) that runs to diagnose or fix a problem — manually or automatically.
- Go to Runbooks → Add Runbook. Name it.
- Add steps: pick a script for each, order them, and optionally mark a step require approval so execution pauses there until a human approves.
- Run it: on the runbooks list click the ▶ action. In the Run modal choose the target, optionally link an incident, and toggle Dry run to log "would execute" without running anything — validate the runbook safely first.
Safety guardrails
| Guardrail | What it does |
|---|---|
| Dry-run mode | Steps log "would execute" without running. |
| Approval gates | A step marked require-approval pauses execution; an authorized approver clicks Approve & resume. Separation of duty: the user who triggered a run can't approve it. |
| Circuit breaker | Per runbook + target: 3 failures in 30 minutes trips it and blocks further auto-triggers for a cooldown. |
| No-op guard | A step with no script is recorded as skipped; a run that did nothing finalizes as failed, never "handled". |
| Backend-host gate | Running scripts on the backend control-plane host is deny-by-default in production. Prefer server targets. |
Auto-remediation
Let a runbook run automatically when a matching incident opens — safely, on the affected server.
- Open the runbook and enable Auto-trigger. Choose the incident type that triggers it (e.g.
DOWN,DEGRADED). - Set the Remediation target. Pick Server (SSH) and select the server the fix should run on. (Backend host is deny-by-default in production, so a server target is what actually works there.) If you leave it on Backend, the incident's own server is used when it has one.
- Save. When a matching incident opens, Thanos resolves the target, runs the runbook over SSH, and — if the fix works — the endpoint recovers and the incident resolves.
Target resolution order: the runbook's explicit server → the incident's own server → the backend host (fail-closed in production). Guardrails that ride along: the server must belong to you (re-verified at trigger time), the circuit breaker is keyed on the target so a shared server can't be hammered, and a single-flight lock prevents duplicate runs for one incident.
Observe-only by default. Auto-remediation honors the REMEDIATION_DRYRUN switch, which defaults to true — auto-triggered runs log "would execute" but don't act. Set REMEDIATION_DRYRUN=false on the backend when you're ready for live remediation. Anomaly-typed incidents never auto-remediate (their trigger is statistical); deterministic types do.
Root-cause analysis
Open an incident and Thanos assembles an RCA: hypotheses with supporting evidence, a dependency chain from the entity graph (deepest root wins), and signals pulled from your log/metric stores (Prometheus, Sentry, OpenSearch, SigNoz). When a failure cascades, the incident is attributed to its cause with a root-cause badge. RCA is display-only — it explains, it never acts on its own.
Analytics & SLI
The Analytics view reports coverage-aware service-level indicators: availability decomposed into good / bad / observed / expected events with an explicit NODATA state, plus latency SLIs (p95 per bucket over pure-UP samples). Coverage below 50% reads as UNKNOWN rather than a fabricated number — the platform would rather say "not enough data" than guess.
SLO targets
Set an objective for a group and track its error budget and burn rate.
- Go to SLO and create a target for an endpoint group.
- Pick the SLI kind (availability or latency), the objective (e.g. 99.9%), the window (7/30/90 days), and — for latency — the threshold. Optionally route burn alerts to a channel.
The budget read-model decomposes the objective into an expected denominator and remaining budget; coverage below 50% reports UNKNOWN. The burn engine watches the 6-hour burn rate on a 5-minute tick and, with two-tick hysteresis, opens a SLO_BURN_TICKET (fast burn) or critical incident (budget nearly exhausted).
SLA contracts
An SLA wraps an existing SLO target in a customer contract: a frozen target percentage, a calendar period, and a credit schedule. Downtime is derived from the incident lifecycle (not raw region events); append-only exclusion windows and downtime intervals are intersected for the period. Reports are frozen and immutable once generated, with append-only amendments; when coverage is under 50%, a report reads INSUFFICIENT_COVERAGE rather than inventing a number.
Status pages
A public page that shows the health of the monitors you choose.
- Go to Status Pages → Add Status Page. Name it, set a slug, and choose Public or private.
- Add the endpoint groups to display.
- Share
/public/status/<slug>. It exposes only published data — no user IDs or emails, and incidents on operator-hidden groups are filtered out.
Observability integrations
Connect external systems as metric and log sources for server metrics and RCA. Under Observability → Add Integration configure a Prometheus, Sentry, OpenSearch, or SigNoz connection, then Test connection. SigNoz is the metrics source for server/node CPU-memory enrichment and the K8s advisor; Sentry and the others feed RCA evidence collection. Credentials are encrypted and every outbound call is SSRF-guarded.
Settings & RBAC
Under Settings set platform defaults — default check interval, accepted statuses, and more. Admins manage users and roles under Admin → Users; role-based checks gate privileged actions (a role-denied action returns not-found for anti-enumeration). AI Providers optionally enables display-only AI incident summaries — the AI layer is off unless configured and can never execute anything.
Configuration reference
Key backend environment variables. Set them in deploy/backend.env or a compose override.
| Variable | Purpose |
|---|---|
JWT_SECRET | Signs user sessions. 64-byte hex. Required. |
ENCRYPTION_KEY | Encrypts stored credentials. 32-byte hex. Required. |
AGENT_TOKEN_PEPPER | Hardens agent token hashing. 32-byte hex. Required. |
CORS_ORIGINS | Comma-separated allowed browser origins. Must include your domain in production. |
WEBSOCKET_ALLOWED_ORIGINS | Allowed WS origins (terminal, heartbeats). Cannot be * in production. |
TRUSTED_PROXIES | Network of your reverse proxy, so client IPs are read correctly. |
REMEDIATION_DRYRUN | Global auto-remediation kill-switch. Default true (observe-only). Set false for live remediation. |
ALLOW_BACKEND_HOST_EXEC | Allow scripts on the backend control-plane host. Deny-by-default in production. |
GOOGLE_CLIENT_ID | Enables Google ID-token verification on the backend. |
FLAP_TRANSITION_THRESHOLD / FLAP_WINDOW_MINUTES / FLAP_COOLDOWN_MINUTES | Flap detection tuning (defaults 6 / 10 / 15). |
Google sign-in
- In Google Cloud Console add your deploy's exact origin to the OAuth client's Authorized JavaScript origins.
- Bake the (public) client ID into the frontend at build time:
VITE_GOOGLE_CLIENT_ID=<id>.apps.googleusercontent.com \ docker compose -f docker-compose.prod.yml up -d --build frontend - Set
GOOGLE_CLIENT_IDon the backend for token verification. Only the client ID is needed — no secret is stored.
Scaling, TLS & backup
Scale the backend
Engines are leader-locked (Postgres advisory locks) and heartbeat consumption is shared (NATS durable pull), so multiple replicas are safe:
docker compose -f docker-compose.prod.yml up -d --scale backend=2
With more than one replica, front them with the frontend container or an external load balancer (one host port can't map to two containers).
TLS & WebSockets
Terminate TLS at your proxy and forward to the frontend container with WebSocket upgrade headers and a long read timeout (the SSH terminal and heartbeats use long-lived WS). Raise client_max_body_size to at least 64m.
Backup & restore
- Back up TimescaleDB (all platform state) daily and store
deploy/*.envsecrets in your secret manager:docker compose -f docker-compose.prod.yml exec timescaledb \ pg_dump -U postgres -Fc thanos > thanos-$(date +%F).dump - Restore into a running stack and restart the backend:
docker compose -f docker-compose.prod.yml exec -T timescaledb \ pg_restore -U postgres -d thanos --clean --if-exists < thanos-YYYY-MM-DD.dump
Redis and NATS are disposable (dedup cache and in-flight heartbeats); losing them costs at most one dedup window. Migrations are forward-only and auto-apply on upgrade — take a DB backup before upgrading.