Getting Started
Prerequisites
| Requirement | Notes |
|---|---|
| PostgreSQL 13+ | History/state store (13+ required — the schema uses gen_random_uuid() and declarative partitioning). The Docker demo bundles it. |
| Prometheus | With node_exporter for CPU/memory/disk metrics |
| Telegram bot, Slack (incoming webhook or bot token), SMTP server, or PagerDuty | @BotFather / Slack incoming webhook or a chat:write bot token for threads in Slack (Telegram threads with no extra credential) / any SMTP relay / a PagerDuty Events API v2 key (paging only — alert groups and failing health checks) |
| LLM API key (optional) | OpenAI or Anthropic; llm.provider: none runs the built-in deterministic engine with no key |
| Docker (optional) | For the quickest setup |
How InfraSigns reaches your metrics
InfraSigns is pull-based: on every cycle it dials out to each source's url,
runs the queries you configured, and summarizes what comes back. Nothing runs
inside your network on its behalf — there is no agent to install. Metrics are
never pushed to InfraSigns; the webhook receiver below is the one inbound path,
and it carries events, not series.
That has one practical consequence, and it is worth checking before you configure a source: the endpoint has to be reachable from wherever InfraSigns runs.
- Self-hosted — InfraSigns runs on your side of the firewall, so a Prometheus on a private address or an internal DNS name is fine. Only the daemon needs to reach it.
- Hosted — the dial leaves our network, so a private address, an internal DNS name or a security group that admits only your own VPC will not work. Expose a reachable endpoint (with a credential — see other authentication schemes), or self-host.
A sign-in front is the same problem wearing different clothes. If the endpoint
sits behind oauth2-proxy, Google IAP or a similar SSO gateway, it answers with an
interactive login rather than with data. That arrives in more than one shape: as a
redirect into the login flow, or as a refusal whose body IS the login page — a
default oauth2-proxy answers a non-browser client 403 and an HTML sign-in page,
with no redirect at all. InfraSigns cannot complete a login in any shape; it can only
present a credential the endpoint accepts directly (a bearer token, basic auth, an
OAuth2 client, an AWS SigV4 key pair). Where the gateway can be configured to
accept one of those, do that; otherwise point InfraSigns at an origin that is not
behind it.
Google IAP is a case where that does not work today. IAP wants a service
account's OIDC ID token minted for a specific target_audience. The OAuth2
jwt-bearer grant InfraSigns ships reads the access_token out of the token
response and discards the id_token, and the claims needed to request an
audience are not exposed in the source configuration — so no oauth2: block can
produce the credential IAP accepts. The only route is to mint the ID token
yourself and paste it into an authorization: block, which expires in about an
hour and has to be re-pasted. Treat an IAP-fronted endpoint as unreachable and
use an origin that is not behind it.
Events are what can travel the other way: an AlertManager (or Grafana, or
CloudWatch via SNS) can push alerts to the webhook receiver, and
a CI pipeline can post deploy notifications to /webhook/deploys, which shares the
same webhook.token. Both carry events, never series — you get incident and
deploy notifications from them, not a metric digest or trends, which need an
endpoint InfraSigns can query.
Try the demo first
Before setting up with real credentials, see what InfraSigns output looks like:
This runs a full digest cycle with mock Prometheus data and prints the result to stdout.
Quick start with Docker
1. Clone and configure
git clone https://github.com/infrasigns/infrasigns
cd infrasigns
cp .env.example .env # fill in LLM_API_KEY and notification tokens;
# DATABASE_DSN already points at the bundled Postgres
2. Edit config/config.yaml for non-secret settings (sources, schedule, etc.) —
Compose mounts this file into the container. The default sources.url points at
the bundled Prometheus.
sources:
- name: production
url: http://prometheus:9090 # bundled Prometheus; or your own URL
# No self-hosted Prometheus? Add a `type: cloudwatch` (AWS),
# `type: digitalocean`, `type: hetzner`, `type: healthcheck` (probe any
# HTTP URL), or `type: loki` (LogQL metric queries) source instead —
# see docs/configuration.md#sources.
llm:
provider: openai
api_key: sk-...
model: gpt-4o-mini
notify:
telegram:
token: "123456:ABC-..."
chat_id: "-100123456789"
reports:
digest:
schedule: "0 8 * * *"
3. Start
The stack includes Postgres, Prometheus, and node_exporter. Point your existing
services at the bundled Prometheus or configure sources.url to point at your own.
Manual setup
Copy the config and replace the compose-specific values: sources.url (the
default points at the compose Prometheus) and database.dsn (replace
'${DATABASE_DSN}' with a literal DSN, or export that variable). Then run:
make build # builds ./cmd/infrasigns and stamps the version `infrasigns version` reports
cp config/config.yaml config/local.yaml # edit: sources.url, database.dsn
./infrasigns migrate up --config config/local.yaml # apply DB migrations
./infrasigns serve --config config/local.yaml # start the daemon
Requires Go 1.26+.
Getting your Telegram chat ID
- Start a chat with your bot or add it to a group
- Send any message
- Open
https://api.telegram.org/bot<YOUR_TOKEN>/getUpdates - Find
"chat":{"id":...}in the response — that's yourchat_id
For groups and channels, the ID is negative (e.g. -100123456789).
Verifying the setup
Trigger a digest immediately without waiting for the cron schedule:
# Docker
docker compose exec infrasigns /infrasigns digest trigger --wait
# Manual
API_TOKEN=<your api.token> ./infrasigns digest trigger --wait
The trigger endpoint requires the bearer token from api.token (see
API authentication); the CLI reads it
from $API_TOKEN or --token. Inside the compose container the variable is
already set from .env.
--wait blocks until the cycle completes and reports the outcome: done when a
digest was delivered, or skipped_no_data when the source produced no metrics
and no alerts (nothing to summarize, so no digest is generated). Omit it for a
fire-and-forget trigger.
What's next
- Configuration reference — all available options
- LLM providers — OpenAI vs Anthropic, model recommendations
- Notifications — Telegram, Slack, email, and PagerDuty setup in detail
- Kubernetes — Helm chart install, secret management, ServiceMonitor