# karma needs Alertmanager 0.22.0, compiles Prometheus into its own binary, and gates its tests behind its linters

> A dashboard for Prometheus Alertmanager that aggregates alerts across instances, reconstructs recent firing history from Prometheus metrics, and manages silences. The mechanics behind those three features are where the operational cost sits: a Go module that depends on Prometheus itself, a three stage container build, and a make test that refuses to run before lint passes.

**prymitive/karma** — Alert dashboard for Prometheus Alertmanager

- Repository: https://github.com/prymitive/karma
- Website: https://demo.karma-dashboard.io/
- Stars: 2,683 · Forks: 203
- Language: TypeScript
- License: Apache-2.0
- Published: 2026-09-28 · Updated: 2026-09-28 · Language: en
- Canonical page: https://hysenlabs.com/projects/prymitive-karma

## Alertmanager 0.22.0 is the floor, and the API above it is not stable

The stated requirement is Alertmanager 0.22.0 or newer, stated once at the top of the documentation. What that number does not tell you is how much drift to expect above it. A dedicated section on supported versions says plainly that Alertmanager's API is not stable yet and can change between releases, and that the list of releases karma is actually tested against lives in a VERSIONS variable inside internal/mock/Makefile. Because those API differences exist, the same documentation then concedes that some features will work differently or simply be missing depending on which release you point karma at. That file is a makefile, not a published compatibility table, so the version you are on has to be checked by reading a build variable in the repository rather than from documentation. The project also names the two things karma exists to add: Alertmanager's own UI is good for browsing alerts and managing silences, and karma fills the gap where a dashboard is wanted instead.

## Alert history is not stored by Alertmanager, so karma re-queries Prometheus

The history panel is the least obvious feature in the project, because it is not reading anything from Alertmanager. Alertmanager keeps no long term store of alert events and offers no way to query historical alerts, so karma takes a different route: with `history:enabled` set to true it reads the `source` field on each alert and uses it to query alert related metrics back on the Prometheus servers that sent those alerts. What comes back is the number of times a given alert group triggered an alert in each of the last 24 hours, drawn as 24 blocks where the darker block means more firings in that hour relative to the others. The cost is stated too: karma has to be able to connect to every Prometheus server sending alerts, and the Prometheus flag `--web.external-url` has to be set to a publicly reachable URL on each one. Miss that and the panel has nothing to query.

## prometheus/prometheus is a direct dependency, not an API client

The Go module lists Prometheus itself as a direct requirement, alongside the client library and the shared model package:

```
	github.com/prometheus/client_golang v1.24.1
	github.com/prometheus/common v0.72.0
	github.com/prometheus/prometheus v0.315.0
	github.com/prymitive/randomcolor v0.0.0-20210705210145-26c3401033a6
	github.com/rogpeppe/go-internal v1.16.0
	github.com/spf13/pflag v1.0.10
```

Compiling Prometheus's own packages is a different commitment from calling its HTTP API, and it is visible in the indirect list that follows: beorn7/perks, fsnotify, grafana/regexp, json-iterator, modern-go/concurrent, modern-go/reflect2 and munnerz/goautoneg all arrive as transitive requirements of that single line. It also explains the reachability requirement in the history feature, since the metrics structures karma renders come from the same tree. The rest of the requirement list is smaller and tells you what else the project does: knadh/koanf with yaml, file, env and posflag providers for configuration, Masterminds/semver for version comparison, hashicorp/golang-lru for caching, go-chi for routing, and spf13/pflag for flags. The module declares go 1.27.0.

## Three build stages end in one static binary on distroless

The container build has three stages and the runtime image contains no JavaScript tooling at all. Stage one builds the UI from node:25.9.0-alpine: it copies ui/package.json and ui/package-lock.json, runs `npm ci`, copies the ui/ directory, and runs `make -C /src/ui build`. Stage two is golang:1.27.1-alpine, which copies the Makefile, the make/ directory, go.mod and go.sum, runs `make -C /src download-deps-go`, and then copies four artifacts forward from the Node stage, the UI's src, dist and mock directories plus its embed.go. Only then does it copy cmd/ and internal/ and build with CGO_ENABLED=0.

```
FROM golang:1.27.1-alpine AS go-builder
ARG VERSION
RUN CGO_ENABLED=0 make -C /src VERSION="${VERSION:-dev}" karma

FROM gcr.io/distroless/static
COPY --from=go-builder /src/karma /karma
EXPOSE 8080
ENTRYPOINT ["/karma"]
```

The final stage is gcr.io/distroless/static, so there is no shell to debug with. The binary exposes port 8080, and the VERSION build argument falls back to the literal string dev, which is also what gets stamped into the org.opencontainers.image.version label. An image built without passing VERSION will report itself as dev.

## make test depends on lint, and lint includes a bootstrap version check

The root makefile is five includes and four targets. `lint` fans out to `lint-go`, to a target named `lint-bootstrap-version`, and to `make -C ui lint`, so linting is not one tool but three steps across the Go tree and the UI tree. `test` then declares `lint` as its first prerequisite before running `make test-go` and `make -C ui test`:

```
.PHONY: test
test: lint
	make test-go
	make -C ui test
```

So there is no way to run the test suite on its own from the root makefile. A formatting problem or a failing bootstrap version check stops the tests from starting, which is a deliberate trade and worth knowing before you debug a red build. The included files are make/vars.mk, make/go.mk, make/cc.mk, make/docker.mk and make/lint-versions.mk, the last of which pairs with the VERSIONS list the supported versions section points at. `clean` removes $(NAME) and $(NAME)-*, plus ui/dist, ui/node_modules, ui/coverage and coverage.txt. `show-version` echoes $(VERSION) and nothing else. A `lint-bootstrap-version` target sitting between the Go and UI linters is consistent with the UI bundle having to exist before the Go build can embed it.

## Shared labels and the one silence that muted the group both fall to the footer

Alerts are grouped the way Alertmanager groups them, preserving its `group_by` configuration, with one important caveat: a separate group is created for every receiver in use, because different receivers can carry different group_by settings. The same alert can therefore appear more than once in the same view. Within a group, only the first few alerts are presented and the rest sit behind minus and plus buttons, with the default count set in the UI settings module. Each group can be collapsed to nothing but its title bar using a toggle in the top right corner.

What lands in the footer is decided by sharing. Labels and annotations common to every alert in the group are moved there, so the grid above shows only what actually distinguishes the alerts. Silence deduplication follows the same rule: when every alert in a group was suppressed by one silence, that silence moves to the footer too. Inhibited alerts, the ones suppressed by other alerts, get a muted button that opens a modal listing the alerts responsible.

## silences:expired surfaces re-silencing candidates only for alerts older than the window

Active alerts show recently expired silences so that an alert which was already silenced once can be silenced again without hunting for it. The behaviour is set by `silences:expired`, and the documented example explains the boundary precisely: with a 10m value, silences that expired in the last 10 minutes are shown, but only for alerts that started firing more than 10 minutes ago. Fresh alerts are excluded, which stops the panel filling with silences for alerts that only just started.

Silence creation is a separate path with its own controls. Since v0.50, a single button click creates a short lived silence to acknowledge an alert, and the documentation points to a separate project, kthxbye, for the variant that should stay until every alert it covers has resolved. Silence ACL rules govern who can create and edit silences, with a dedicated ACLs document behind that. Dead Man's Switch support arrived in v0.78: karma can be told to expect an always firing alert on a given Alertmanager, configured through `healthcheck:filters`, and raises an error in the UI when none is found.

## Which release gave you which feature, from 0.7.0 to 0.78

The features carry version stamps, and the sequence explains the shape of the project. v0.7.0 brought aggregation and deduplication across multiple Alertmanager instances, whether they run in HA or separately: duplicate alerts are filtered out, each alert is tagged with the instances it was found at, and that tag becomes the `@alertmanager` filter. The tag only appears when more than one instance is configured, and an HA setup adds `@cluster` with a custom name per cluster. Multi-instance setup therefore changes what you can filter on, not only what you see.

Two later additions change the surface rather than the plumbing. v0.52 brought light and dark themes, following the browser preference through prefers-color-scheme unless told otherwise. And the alert overview modal, opened from the counter in the top left corner, summarises the top label values across current alerts, while label based multi-grid adds another grouping layer from the configuration modal, giving each value of a chosen label its own grid plus one extra grid for alerts missing the label entirely.

The releases themselves have moved at a steady, unhurried clip: v0.131 on 2026-05-18, v0.132 on 2026-08-05, v0.133 on 2026-09-21. The project has been on the 0.x line throughout, and it began as a Cloudflare project named unsee, rewritten with React and renamed, with the API declared incompatible with the original because of the rewrite.

## Conclusion

karma fits teams running more than one Alertmanager, or teams that need silences managed from a UI with ACLs rather than by hand. Three things to verify before you point it at production. The floor is Alertmanager 0.22.0, and the API above that floor is still called unstable, so check the VERSIONS list in internal/mock/Makefile against the Alertmanager build you actually run and expect some features to differ. The history view is not Alertmanager history, it re-queries each Prometheus server that sent the alert, which means every one of them needs a reachable --web.external-url or the panel stays empty. And a karma build pulls in github.com/prometheus/prometheus v0.315.0 as a direct dependency, so your Go module graph carries Prometheus's own tree. For a single Alertmanager with no history requirement, the built-in Alertmanager UI is less machinery to own. The last push to main is dated 2026-09-22 and v0.133 was published on 2026-09-21.

## FAQ

### What Alertmanager version does the karma dashboard need?

The stated requirement is Alertmanager 0.22.0 or newer. The documentation also says Alertmanager's API is not stable between releases and points to a VERSIONS list inside internal/mock/Makefile for the releases karma is tested against, with the caveat that some features differ or are missing on older ones.

### Where does karma get alert history from?

Not from Alertmanager, which stores no long term alert events. With history:enabled set to true karma reads the source field of each alert and queries alert related metrics on the Prometheus servers that sent them, then renders the number of firings per hour over the last 24 hours as 24 blocks.

### Does karma need Prometheus reachable to show history?

Yes. The documentation states karma must be able to connect to all Prometheus servers sending alerts for the history feature to work, and that the Prometheus flag --web.external-url must be set to a publicly reachable URL on each of them.

### Does the karma container image contain Node.js at runtime?

No. A Node stage builds the UI first, the Go stage copies four UI artifacts forward and builds with CGO_ENABLED=0, and the final image is gcr.io/distroless/static with a single /karma binary, port 8080 exposed, and no shell.

## Sources

- [License: Apache-2.0](https://github.com/prymitive/karma/blob/main/LICENSE)
- [Project website](https://demo.karma-dashboard.io/)
- [prymitive/karma on GitHub](https://github.com/prymitive/karma)
- [README](https://github.com/prymitive/karma/blob/main/README.md)
- [Releases](https://github.com/prymitive/karma/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/prymitive-karma
