Run the Miden Dashboard UI with Guardian
Stand up a local Guardian server and the operator dashboard UI
(0xMiden/guardian-dashboard)
side by side with Docker Compose, then log in as an operator and browse
accounts end to end.
This is the assembly layer that ties together the existing reference docs — it
does not re-explain them. For the dashboard's trust model, the
challenge→session auth flow, the allowlist format, and the permission
vocabulary, see DASHBOARD.md. For the authoritative
meaning of any server variable, see CONFIGURATION.md.
The stack bundles Postgres for Guardian's state and metadata — the published
Guardian image is built with the postgres feature, so a database is required
(this guide is a realistic self-hosted base, not a throwaway filesystem toy). The
ACK signer uses an in-memory key held in a local keystore — fine for a single
host; production deployments move it to AWS Secrets Manager / KMS (see the
AWS-managed signers guide). The dashboard is a
Next.js app the upstream repo ships without an image, so Compose runs it from a
source clone on the stock node image.
How the pieces talk: the browser only talks to the dashboard; the dashboard's
Next.js backend holds the operator's Falcon private key and signs the
challenge→session flow against the Guardian server itself. The server is
reached over the Compose network at http://server:3000, so it needs no CORS
configuration here.
Prerequisites
- Docker.
- A clone of the dashboard inside this guide directory. From here:
By default this guide expects it at
git clone https://github.com/0xMiden/guardian-dashboard./guardian-dashboard. Point elsewhere withGUARDIAN_DASHBOARD_PATHin.env. - A free Clerk application — the dashboard uses Clerk for human sign-in and cannot start without its keys.
1. Generate an operator key
The dashboard ships a helper that emits everything you need — the private
key and commitment for the dashboard, and the public key for the
Guardian allowlist. From your guardian-dashboard clone:
npm install
npx tsx scripts/generate-operator-key.ts
It prints two labelled blocks: a GUARDIAN_OPERATOR_PRIVATE_KEY /
GUARDIAN_OPERATOR_COMMITMENT pair, and a ["0x…"] public key. These are not
env vars you set directly — in step 3 the private key and commitment go into the
privateKey and commitment fields of the GUARDIAN_ENDPOINTS entry in .env,
and the public key goes into operators.json. Keep the private key secret.
(Falcon keys generated by the multisig SDK or the
operator-smoke-web UI work too, as
long as you can export all three values.)
2. Configure Clerk
In your Clerk app, copy the test publishable and secret keys for step 3. Then create the admin user (or use your own) and set its public metadata so the dashboard authorises it for this node:
{ "role": "admin", "endpointIds": ["local"] }
endpointIds must include the endpoint id you use in GUARDIAN_ENDPOINTS
below (local here). Without it the dashboard signs you in but shows no nodes.
3. Configure the environment
From this directory:
cp .env.example .env
cp operators.example.json operators.json
In .env set:
POSTGRES_PASSWORD— a strong, stable, URL-safe value for the bundled Postgres.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEYandCLERK_SECRET_KEY— the test keys from step 2.GUARDIAN_ENDPOINTS— fillcommitmentandprivateKeyfrom step 1. Leaveurlashttp://server:3000(the server's address on the Compose network) and keepnetworkequal toGUARDIAN_NETWORK_TYPE.
In operators.json replace 0x<your-falcon-operator-pubkey> with the public
key from step 1. The default entry grants dashboard:read and accounts:pause;
trim the permission set if you only need read access (see the permission
vocabulary in DASHBOARD.md). The server
hot-reloads this file on every challenge and authenticated request, so you can
edit operators without restarting.
4. Run
From this directory (Compose auto-discovers docker-compose.yml and .env):
docker compose up
The first dashboard boot runs npm install inside the container and is slow;
subsequent boots reuse the cached node_modules volume. The server starts
first; the dashboard only calls it once you sign in, by which point it is ready.
5. Validate
Confirm the server is live and serving its ACK keys:
curl -s localhost:3000/pubkey | jq .
Then open the dashboard at http://localhost:3001, sign in through Clerk,
select the Local Guardian endpoint, and confirm the account list loads. A
successful list proves the full path: Clerk sign-in → operator
challenge→session against the server → an authenticated /dashboard/accounts
call.
Troubleshooting
| Symptom | Likely cause |
|---|---|
| Sign-in works but no endpoint is selectable | Clerk public metadata missing endpointIds, or it omits the id from GUARDIAN_ENDPOINTS |
| Endpoint selected but accounts list is empty or 401s | Operator public key not in operators.json, or network in GUARDIAN_ENDPOINTS ≠ GUARDIAN_NETWORK_TYPE |
| Dashboard fails to start citing Clerk keys | NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY / CLERK_SECRET_KEY unset in .env |
Dashboard container exits / /app is empty | GUARDIAN_DASHBOARD_PATH doesn't point at your guardian-dashboard clone |
First docker compose up hangs on the dashboard | npm install running on first boot — wait it out; later boots are fast |
| Port 3001 already in use | Stop the conflicting process, or remap the dashboard port in docker-compose.yml |
See TROUBLESHOOTING.md for the full server
error-code playbook.
Operations checklist
When standing up the dashboard against a stack:
- At least one operator with
dashboard:readin the allowlist — otherwise the dashboard is unreachable. -
networkinGUARDIAN_ENDPOINTSmatchesGUARDIAN_NETWORK_TYPE. - Clerk operator metadata
endpointIdsincludes every endpointidthe user should see. - A fresh sign-in → endpoint select → account list round trip succeeds before considering the stack live.