A local dashboard for Dapr developers that offers a live view of everything Dapr running on your machine, plus guided builders for authoring Dapr component and resiliency YAML.

The Diagrid Dapr Dev Dashboard is a companion for local Dapr development. It inspects the

apps you start with dapr run / dapr run -f, Aspire, Docker Compose, or Dapr

Testcontainers (e.g. Spring Boot apps run with mvn spring-boot:test-run and

dapr-spring-boot-starter-test), and surfaces everything about them — sidecars, workflows,

actors, subscriptions, components, resiliency policies, configurations, and logs.

It also helps you author Dapr resources. The Component Builder walks you through picking a component type from the full Dapr catalog, filling in its metadata fields, and choosing an authentication profile; the Resiliency Builder composes resiliency policies (timeouts, retries, circuit breakers) and applies them to targets (apps, actors, components). Both wizards end in a YAML preview you can copy or download into your project.

The dashboard ships as a standalone binary published on GitHub Releases.

Install (one-liner):

macOS / Linux — installs to ~/.local/bin

curl -sSL https://raw.githubusercontent.com/diagridio/dev-dashboard/main/scripts/install.sh | shWindows (PowerShell) — installs to %LOCALAPPDATA%\Programs\diagrid-dev-dashboard

iwr -useb https://raw.githubusercontent.com/diagridio/dev-dashboard/main/scripts/install.ps1 | iexTo pin a specific version, set VERSION before piping:

macOS / Linux

curl -sSL https://raw.githubusercontent.com/diagridio/dev-dashboard/main/scripts/install.sh | VERSION=vX.Y.Z shWindows

$env:VERSION='vX.Y.Z'; iwr -useb https://raw.githubusercontent.com/diagridio/dev-dashboard/main/scripts/install.ps1 | iexIf the install directory is not on your PATH, the script prints the export line to add.

Install with Go (≥ 1.26):

go install github.com/diagridio/dev-dashboard@latestNote:

go installnames the binary after the module path, so it producesdev-dashboard, notdiagrid-dev-dashboard— rename it afterwards (mv "$(go env GOPATH)/bin/dev-dashboard" "$(go env GOPATH)/bin/diagrid-dev-dashboard") or use the install script above.

Manual download:

Download the archive for your platform from the GitHub Releases page, extract it, and place diagrid-dev-dashboard (or diagrid-dev-dashboard.exe) on your PATH. Verify with:

diagrid-dev-dashboard --version# Start on the default port (9090) and open your browser automatically

diagrid-dev-dashboard# Start on a custom port

diagrid-dev-dashboard --port 8080# Enable diagnostic logging to stderr (server startup, app discovery, state-store connection, log streams, workflow operations)

diagrid-dev-dashboard --verboseNo additional setup is needed: the dashboard discovers running Dapr apps the same way

dapr list does, so anything started with dapr run / dapr run -f, Aspire, Docker

Compose, or Dapr Testcontainers shows up within one refresh cycle. Testcontainers apps

(e.g. a Spring Boot app under mvn spring-boot:test-run) need zero configuration: the

dashboard finds the Testcontainers-managed daprd container, pairs it with your host app

process, and even reads workflows from an in-memory state store via the sidecar itself.

Tip: once the dashboard is open, try the Konami code (↑ ↑ ↓ ↓ ← → ← → B A) and have some fun! ;)

With --mode/DEVDASHBOARD_MODE unset (the default for host use), the dashboard performs the

complete scan across all discovery sources described above. Setting a mode restricts every

dashboard surface — applications, workflows, state stores, the Control Plane view, and log

targets — to a single source; filters are exclusive and never combined:

- --mode dapr-run— host- dapr runprocesses only (Control Plane shows the- dapr initcontainers).

- --mode compose— Docker Compose containers only (Control Plane shows compose-run placement/scheduler).

- --mode test-containers— Testcontainers discovery only (no control-plane detection yet).

- --mode aspire— Aspire resources only. Inside an AppHost-managed container (the- DEVDASHBOARD_APP_*contract is present) this is the container serving posture described below; on a plain host it filters the process scan to Aspire-managed apps.

compose and test-containers require a container runtime (docker or podman) and fail at

startup without one. --bind (default 127.0.0.1, 0.0.0.0 in aspire container posture)

controls the listen address alongside --port.

The dashboard also ships as a container image, purpose-built for embedding inside a

.NET Aspire AppHost via the

diagrid-labs/dashboard-aspire hosting

integration (which will be rewritten against the contract below). It also runs standalone

with a hand-written docker run.

Image: ghcr.io/diagridio/dev-dashboard, tagged :X.Y.Z (in lockstep with binary

releases) and :latest. Prerelease tags (e.g. v1.5.0-rc.1) publish their own version tag

but do not move :latest. The image bakes in DEVDASHBOARD_MODE=aspire and serves on port

8080 bound to 0.0.0.0.

In aspire mode, discovery is restricted to the env contract below (no host process scan, no Docker Compose scan), and the following are disabled: app lifecycle controls (start/stop/restart), the control-plane page, log tailing, self-update/update-check, and automatic browser opening.

Try it:

docker run --rm -p 8080:8080 \

-e DEVDASHBOARD_APP_COUNT=1 \

-e DEVDASHBOARD_APP_0_ID=myapp \

-e DEVDASHBOARD_APP_0_DAPR_HTTP=http://host.docker.internal:3500 \

ghcr.io/diagridio/dev-dashboard:latestMode switch:

App discovery — one set of _<i>_* vars per app, for i = 0..DEVDASHBOARD_APP_COUNT-1:

Validation is fail-fast at startup: a missing or non-numeric DEVDASHBOARD_APP_COUNT, any

missing required per-app var, or an unparsable DAPR_HTTP URL exits with an error naming the

exact variable.

Security note: in aspire mode the dashboard drops the loopback Host check (the container

is addressed by its Docker-network name or a published port). Without DEVDASHBOARD_ALLOWED_HOSTS

it accepts any Host, which means a malicious web page using DNS rebinding can reach the API even

when the port is published only to localhost. Setting DEVDASHBOARD_ALLOWED_HOSTS to the

hostnames the dashboard is served under closes that hole (loopback names are always allowed).

Mutating requests are still protected by a normalized same-origin check. The dashboard is a local

development tool — never expose it publicly.

Serving and features:

Precedence everywhere is flag > env > posture default.

On startup the dashboard checks GitHub for a newer release. If one exists, it prints a notice as the first line of output and the web UI shows an Update available indicator next to the version number in the Resources panel. The check is best-effort: it is skipped for source/dev builds and fails silently when offline.

Update to the latest release (no-op if already current)

diagrid-dev-dashboard updateInstall a specific version (can downgrade or reinstall)

diagrid-dev-dashboard update 1.2.0update downloads the release archive for your platform, verifies its SHA256

against the release checksums.txt, and atomically replaces the running binary.

Restart any running dashboard to use the new version.

If the dashboard does not behave as expected, run it with --verbose to print diagnostic logs to stderr:

diagrid-dev-dashboard --verboseLogs are grouped by component= (values: server, discovery, workflow, registry, reconciler) and use levels INFO (normal milestones), WARN (degraded but still working, e.g. a state store that failed to initialise), and ERROR (an operation failed, e.g. the server could not bind its port). Without --verbose, no diagnostic logs are emitted.

Developers use the dashboard to observe and debug Dapr apps while building locally:

- See what's running — a live table of all running apps/sidecars: app id, health, runtime/language, app/HTTP/gRPC ports, daprd + app PIDs, age, and owning run process.

- Inspect an application — drill into a single app for its ports, PIDs, command, resource/config paths, runtime metadata, enabled features, and loaded components.

- Debug workflows — browse workflow executions across all apps with status filters and search, then open a run to watch its live event history, input/output, custom status, and a continuously-ticking wall-clock while it runs.

- Clean up workflows — terminate and/or purge individual or bulk workflows, with an explicit "force delete" fallback for stuck/orphaned state.

- Browse, add & clean state records — the State page lists the records in any connected

state store: key, app prefix, value preview, size, version (etag), and TTL, paginated over the

backend's own key cursor, with row expansion for the full value and a Decode base64 toggle for

encoded values. + New record writes a record under <app-id>||<key>— the same key shape Dapr writes — and refuses a key that already exists unless you opt into overwriting; records can be deleted individually or by multi-select. There is no in-place edit: overwrite the key instead. Two limitations worth knowing: workflow history and actor state are hidden by default (they live in the same keyspace as your app's records — use Show internal keys to reveal them), and there is no "last modified" column — Dapr's state components do not expose a modification timestamp, so none can be shown. The Version column is the backend's etag: it changes on every write, but it is not a time. Stores that cannot be opened directly — an in-memory store inside a Testcontainers app, for example — cannot be browsed at all: Dapr's state API has no way to enumerate keys, so there is nothing to page over.

- Review actors & subscriptions — global pages aggregating active actor types and pub/sub subscriptions across all apps, each linkable back to the owning application.

- Read components & configurations — read-only YAML viewers, enriched with which apps loaded each component; components that only exist inside a Testcontainers daprd container are extracted and shown too, with a container-prefixed path.

- Build component YAML — a guided wizard over the full Dapr component catalog: pick a type, fill in its metadata fields (with per-field docs and defaults), choose an authentication profile, then copy or download the generated YAML.

- Build resiliency policies — compose named timeouts, retries, and circuit breakers, apply them to targets (apps, actors, components), and export the resiliency spec as YAML.

- Manage workflow state-store connections — on the Components page, a recent-connections panel

(with component file paths) lets you add, edit, and disconnect the state stores the dashboard

reads workflows from. Auto-detected stores appear automatically; disconnecting one is durable —

it stays hidden across restarts unless it becomes the active store again. The store workflows

are currently read from can't be removed. Manual connections are saved to

~/.dapr/dev-dashboard/connections.yaml(mode0600). When more than one store is known, a selector on the Workflows page lets you switch which one you browse.

- Tail logs — per-app daprd + app logs streamed live (SSE) with level coloring, keyword highlight, and a follow toggle.

The UI is built for fast scanning and debugging: deep-linkable views, a global autorefresh control that doubles as a backend-connection indicator (data polling pauses while the backend is unreachable and resumes on recovery), full keyboard operability, and cross-navigation between related entities (app → component → "loaded by" app, etc.).

The dashboard is a local development tool, and only that:

- Not for Kubernetes. It is not intended to run inside a Kubernetes cluster. It discovers

apps the way dapr listdoes — from the local process table and local container runtime — which has no meaning in-cluster.

- Not for production. It is not intended for production, staging, or any shared/hosted environment. It has no authentication, authorization, or multi-user model, and it reads local files and state stores with the privileges of the user who runs it.

- Not a control plane or deployment tool. It observes and helps you author YAML locally; it does not deploy apps or manage remote Dapr installations.

Run it on your own machine, alongside the apps you start with dapr run, Aspire, Docker

Compose, or Dapr Testcontainers.

Prerequisites: Go ≥ 1.26 and Node.js 20 (with npm). The binary embeds the React SPA via

go:embed, so the web assets (web/dist) must be built before the Go binary — make build

does both in the right order.

macOS / Linux:

make build # builds web/dist, then the Go binary at bin/diagrid-dev-dashboard

./bin/diagrid-dev-dashboardEquivalent manual steps (if you don't have make):

cd web && npm install && npm run build && cd ..

go build -o bin/diagrid-dev-dashboard .

./bin/diagrid-dev-dashboardWindows (PowerShell): make is usually unavailable, so run the steps directly:

cd web; npm install; npm run build; cd ..

go build -o bin/diagrid-dev-dashboard.exe .

.\bin\diagrid-dev-dashboard.exeTo build for a sub-path mount, set DASH_BASE_PATH before building (see

Mounting under a sub-path). On Windows that is

$env:DASH_BASE_PATH='/dashboard/' before the npm run build step.

Other useful targets: make test (Go unit + web suites), make test-go, make test-web,

make test-integration, make test-e2e, make tidy.

The dashboard is a passive observer: it discovers your app the same way dapr list does

and reads workflow data directly from your Dapr state store. You don't point it at your app —

you just run both on the same machine.

Prerequisites:

- dapr inithas been run. This creates- ~/.dapr/components/statestore.yaml(a Redis store with- actorStateStore: "true") and starts Redis. That- actorStateStorestore is what Dapr Workflows persist to, and what the dashboard reads.

- A Dapr workflow app — e.g. the Dapr Workflow quickstart, or any app using the Workflow API.

Steps:

- Run your workflow app with Dapr (from the app's directory):

dapr run --app-id order-processor --app-port 6001 -- <your app start command> # or, for a multi-app project: dapr run -f .

- Trigger at least one workflow instance (via the app's endpoint / the quickstart's flow). The dashboard only shows state that already exists — an idle store shows an empty list.

- Start your from-source build:

./bin/diagrid-dev-dashboard # opens http://localhost:9090

- In the UI: the Apps table shows your app (health, ports, PIDs); the Workflows page lists instances read from the state store — open one for its live event history, input/output, status, and a ticking wall-clock. You can also terminate / purge an instance (with the force-delete fallback).

If the Workflows page is empty: the dashboard auto-detects state-store components from

~/.dapr/components and from the live --resources-path of running apps, then uses the one

marked actorStateStore: "true" (falling back to the first detected). Check:

- If detection is ambiguous (several stores), either pick one with the store selector on the

Workflows page, or point it explicitly:

./bin/diagrid-dev-dashboard --statestore ~/.dapr/components/statestore.yaml. You can also add a store by hand via the connection manager on the Components page.

- Workflow keys are namespaced; the dashboard defaults to default. For another namespace, pass--namespace <ns>.

- Only Redis / PostgreSQL / SQLite / MongoDB state stores can be opened directly. Apps whose store

the dashboard can't open (e.g. state.in-memory, or a Testcontainers app whose store lives inside the container) are served via their sidecar's gRPC workflow API instead — this requires Dapr ≥ 1.17 and only works while the sidecar is running.

- Confirm the app actually persisted a workflow (an empty store → empty list).

Testcontainers apps (e.g. the Java quickstarts run with mvn spring-boot:test-run) need

none of the store setup above: workflows, components, and app details all come from the

Testcontainers-managed sidecar itself.

There are four suites: Go unit tests, Go integration tests, the web

(frontend) tests, and an opt-in Go e2e suite. The unit and web suites are self-contained.

The integration suite runs its state-store and workflow tests against a temporary SQLite

database plus real Redis, PostgreSQL, and MongoDB containers (via testcontainers-go), so it

needs Docker or Podman for full coverage; without one, the container-backed tests skip. The

e2e suite drives a real daprd and is local-only — it skips automatically when Dapr is not

installed (see below).

Prerequisites: Go ≥ 1.26 (Go tests) and Node.js 20 with npm (web tests).

Run everything (macOS / Linux):

make test # Go unit tests (with -race) + web testsmake test runs make test-go then make test-web. It does not run the Go integration

tests — run those separately (see below).

Go unit tests — gated by //go:build unit:

make test-go # = go test -tags unit -race ./...

# or directly:

go test -tags unit ./...

go test -tags unit -race ./cmd/... # one package, with the race detector(make test-go uses gotestsum for nicer output if it's installed, otherwise plain go test.)

Go integration tests — gated by //go:build integration; they exercise the state-store and

workflow read paths (including a parity test of the workflow list/stats/detail against all four

supported backends), the parsed sidecar /v1.0/metadata, and the full assembled HTTP server.

Backends run as containers through testcontainers-go (skipped when no container runtime is

available) alongside a temp SQLite DB. They run in CI but are not part of make test:

make test-integration # = go test -tags integration -race ./...

# or directly:

go test -tags integration ./...Some integration tests use golden files (testdata/golden/*); regenerate them after an

intentional shape change with -update, e.g.

go test -tags integration ./pkg/workflow -run Golden -update.

Go e2e tests — gated by //go:build e2e; they run a real Dapr workflow app under

dapr run and read its state back through the dashboard's own packages, validating against

state authored by a live runtime. They require a local Dapr install (dapr init) — dapr on

your PATH and daprd on PATH or in ~/.dapr/bin — and skip automatically when Dapr is

not found. They are local-only and not run in CI:

make test-e2e # = go test -tags e2e ./...Web tests — Vitest:

make test-web # = cd web && npm install && npm test (vitest run)

# or from web/:

cd web

npm install

npm test # single run

npm run test:watch # watch modeWindows (PowerShell) — make is usually unavailable, so run the commands directly:

go test -tags unit -race ./...

go test -tags integration ./...

cd web; npm install; npm test; cd ..Tip: the Go tests are build-tag-gated, so a plain

go test ./...(without-tags unit,-tags integration, or-tags e2e) reports "no test files" for most packages. Always pass the tag.

make lint # lint-go (gofmt + go vet) + lint-web (eslint)

make lint-go # = gofmt check + go vet -tags unit ./...

make lint-web # = cd web && npm install && npm run lint (eslint .)The Go checks (gofmt, go vet) and the web eslint run in CI on every push and pull request.

Pre-commit hook (optional): install a hook that lints only your staged files before each commit:

make hooks # symlinks .git/hooks/pre-commit -> scripts/pre-commitIt runs gofmt/go vet on staged Go files and eslint on staged web/ files. Bypass it for a

single commit with git commit --no-verify.

For maintainers with push access. Releases are built and published by the

releaseGitHub Actions workflow: pushing avX.Y.Ztag runs GoReleaser, which compiles the cross-platform archives pluschecksums.txtand publishes them to a GitHub Release. The version (diagrid-dev-dashboard --version) is injected from the tag via build-time ldflags. The same tag-driven run also builds and pushes the multi-arch container image toghcr.io/diagridio/dev-dashboardvia goreleaser. Prerelease tags (any-suffix, e.g.v1.5.0-rc.1) push their version-tagged image but skip the:latestmanifest, so release candidates never reach:latestconsumers.

Because go install cannot run npm, the release tag commit must embed the prebuilt

web/dist. scripts/release.sh handles this: it builds the SPA, creates a detached commit

that force-adds web/dist (past .gitignore), tags it, and returns you to your branch — so the

tagged commit ships the full UI for go install while main stays free of built assets.

Cut a release (macOS / Linux, or Git Bash / WSL on Windows — release.sh is a POSIX sh script):

- Be on an up-to-date mainwith a clean working tree.

- Build + tag the release:

It builds the SPA, creates the tag on a detached commit embedding the UI, and prints the push command.scripts/release.sh vX.Y.Z

- Push the tag to trigger the release workflow:

git push origin vX.Y.Z

- Wait for the releaseworkflow to finish. It publishes a GitHub Release with one archive per platform +checksums.txt. After that, the install one-liners andgo install github.com/diagridio/dev-dashboard@vX.Y.Zresolve to the new version.

Validate locally before tagging (optional, requires GoReleaser v2):

make release-check # validate .goreleaser.yaml

make release-snapshot # build a local snapshot into dist/ without publishingThe release matrix is 5 archives: macOS and Linux (amd64 + arm64) and Windows (amd64). There is no native Windows/arm64 build — Windows on ARM uses the amd64 build via emulation.

The dashboard is a single Go binary that embeds a React SPA and talks to your local Dapr sidecars and state store.

For the full architecture — how discovery, state-store election, the reconciler, the HTTP layer, and the SPA fit together, plus a guide to extending each part — see ARCHITECTURE.md. The summary below is the quick tour.

┌───────────────────────────────────────────────────────────────┐

│ diagrid-dev-dashboard (single Go binary) │

│ │

│ cmd/ cobra root, flags, serve boot, │

│ connection registry + reconciler │

│ pkg/server chi router + go:embed SPA │

│ pkg/discovery standalone.List() + /v1.0/metadata │

│ pkg/workflow list / history / purge │

│ pkg/state state record listing / add / delete │

│ pkg/statestore client (redis / postgres / sqlite / mongodb) │

│ pkg/controlplane docker/podman inspect + lifecycle │

│ pkg/metadata component metadata catalog │

│ pkg/resources component + configuration YAML loader │

│ pkg/logs file tail → SSE │

│ web/ React + Vite SPA → dist/ (embedded) │

└───────────────────────────────────────────────────────────────┘

│ HTTP /v1.0/* │ files / TCP │ docker/podman

▼ ▼ ▼

running daprd ~/.dapr, resource paths, control-plane

sidecars state store backend containers

Components

- Backend (Go + chi) — exposes a REST + JSON API (with SSE for log/stream tails) and serves the embedded SPA. Each domain lives in an isolatedpkg/*package (aservice.goplus its response types); the HTTP layer lives inpkg/server, one file per domain. No domain package depends oncmd/.

- Frontend (React + TypeScript + Vite) — a single-page app built to static assets and

embedded into the binary via go:embed. Uses TanStack Query for polling/caching, headless accessible primitives styled in-house, and a custom lightweight read-only YAML highlighter (not Monaco). Client-side History-API routing (react-router-dom); the Go server falls back toindex.htmlfor unknown paths and is base-path-aware. List virtualization is planned, not yet in v1. The UI styling conventions (design tokens, page anatomy, component classes) are documented inweb/STYLEGUIDE.md.

Key dependencies & data sources

- App discovery reuses github.com/dapr/cli/pkg/standalone(the same mechanism asdapr list).standalone.List()reads the local process table and is the source of truth for existence/ports/PIDs; the/v1.0/metadatacall per sidecar is enrichment (runtime version, components, actors, subscriptions, extended metadata) and degrades gracefully when a sidecar is down. A/v1.0/healthzcheck per sidecar drives the health badge — computed on demand during each/api/appsfetch (no separate background poller), so its refresh cadence follows the UI's autorefresh interval. A second scanner discovers Dapr apps running under docker compose by inspecting compose-labelled containers (app id and ports from the daprd argv, logs streamed from the container runtime). A third scanner discovers Dapr Testcontainers sessions (org.testcontainers-labelled daprd containers, e.g. fromdapr-spring-boot-starter-test): the randomly-published HTTP/gRPC ports are re-read every poll, the host app process is paired via the app port (real PID, uptime, runtime), and the component YAML declared in test config is extracted from the container so it appears on the Components page. All sources are merged, so one failing never hides the others.

- Workflows are read from the detected state store backend (Redis / PostgreSQL /

SQLite / MongoDB); a client is built from the auto-detected component YAML. When the dashboard cannot

open an app's store — Testcontainers apps (whose store lives inside the container,

state.in-memoryincluded), or any app when no store is openable — workflows are read live from the sidecar's gRPC workflow API (Dapr ≥ 1.17) instead, per app. Purge uses the official Dapr workflow API when reachable, with direct state-store key deletion as an explicit force fallback.

- Connections registry — the state stores the dashboard can read from are tracked in a

registry: auto-detected component refs plus any connections added in the UI, persisted to

~/.dapr/dev-dashboard/connections.yaml(mode0600). Thepkg/metadatacatalog drives the add/edit forms, the workflow backend connects to the selected store lazily (on demand), andsecretKeyRefmetadata is resolved through local secret stores (secretstores.local.file/secretstores.local.env).

- Resources (components + configurations) are loaded from ~/.daprand live--resources-pathdirectories read from daprd args.

- Logs are tailed from ~/.dapr/logs/*and theappLogPath/daprdLogPathreported in extended metadata, then streamed to the SPA over SSE.

- Control plane is inspected through the resolved container runtime (docker, elsepodman):dapr_scheduler/dapr_placementare the self-hosted containers the dashboard can start/restart/stop (allowlisted to those names). Container logs stream over SSE viadocker logs -f.

- News (optional) — the Resources sidebar pulls the Diagrid product feed, proxied and

cached behind the backend's own GET /api/newsendpoint so the SPA only ever talks to its own origin.

Portability — all logic lives in pkg/* domain packages with no dependency on cmd/,

the server mounts as a chi sub-router, and the SPA is an embedded fs.FS, so the whole

thing can later be re-mounted under a diagrid dashboard subcommand.

For the full architecture and extension guide, see ARCHITECTURE.md; for the

original design rationale, see

docs/superpowers/specs/2026-06-25-dev-dashboard-design.md.

Contributions are welcome! See CONTRIBUTING.md for how to report issues,

set up a development environment, and submit pull requests. All commits must be signed off

per the Developer Certificate of Origin (git commit -s).

Copyright © Diagrid Inc. Licensed under the Apache License 2.0.