Skip to content

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:

git clone https://github.com/infrasigns/infrasigns
cd infrasigns
go run ./cmd/demo

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

docker compose up -d

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

  1. Start a chat with your bot or add it to a group
  2. Send any message
  3. Open https://api.telegram.org/bot<YOUR_TOKEN>/getUpdates
  4. Find "chat":{"id":...} in the response — that's your chat_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