MDK Logo

Build your first app with a UI

[⏱️ ~15 min] From git clone to your own MDK app, with its page in the MDK dashboard, reading a mock miner

If Kernel, Worker, or Gateway are unfamiliar terms, read the architecture overview first. You don't need it to finish this page: every step is a command.

Overview

This quickstart takes a clean machine with Node.js to a running MDK project of your own, using only the mdk CLI: one Worker reading a mock miner, one app with its own HTTP route and React page, and the MDK dashboard that loads that page. You write no code until the last step, where you change the starter page and see it update.

What you'll have at the end:

  • A Worker, rig, reading a mock miner that speaks the real wire protocol
  • An app, ops, serving GET /apps/ops/api/ops/summary through the Gateway, with a starter page built from MDK UI components
  • The MDK dashboard at http://localhost:3030, showing the app's page and its card on Home

The project is a directory of its own:

my-site/
├── mdk.yaml              # the stack spec: Kernel, Gateway, Workers, apps
├── package.json          # links workers/ and apps/<name>/ by file: dependencies
├── workers/
│   └── rig/              # the Worker: contract, handlers, mock device
├── apps/
│   ├── ops/              # the app: route table, controllers, ui-src/ and the built ui/
│   └── dashboard/        # the MDK dashboard (UI shell)
└── .mdk/                 # runtime state written by `mdk run`, disposable

Prerequisites

  • Node.js >=24
  • npm 11 (below 12)
  • Network access to npm and to GitHub, where the dashboard template is fetched from
  • jq, to read the access token out of the sign-in response
  • macOS or Linux, with a bash or zsh shell
  • About 2 GB of free disk space
  • Ports 3847 (Gateway), 3848 (Kernel) and 3030 (dashboard) free. mdk run fails if the Gateway or Kernel port is taken, so stop any stack still running from an earlier demo first. The dashboard moves to another port on its own.

Time

The commands themselves take a few minutes on a fast connection with an empty npm cache; the longest waits are the dashboard's install and the app's starter UI build. A slower machine or network takes several times longer and still fits in 15 minutes with time to read each step. A second project skips the first part.

Install the CLI

npm install -g @tetherto/mdk-cli
mdk --version

Every mdk command below runs from the project directory, in whichever terminal you open. The install guide covers npx for one-off use, and the source checkout for working on the CLI itself.

Create a project

Make a directory anywhere and write its stack spec:

mkdir ~/my-site && cd ~/my-site
cat > mdk.yaml <<'YAML'
apiVersion: mdk/v1
kind: Stack
metadata:
  name: my-site
spec:
  network:
    dht: { host: 127.0.0.1, port: 49737 }
  kernel: { port: 3848 }
  gateway: { port: 3847 }
  workers: []
  apps: []
YAML

mdk onboard writes the same file interactively and can add Workers, apps and the dashboard for you. It needs an interactive terminal.

spec.network.dht keeps the stack on a private bootstrap on loopback, so it needs no internet access. Drop it and every component joins the public HyperDHT through *.hyperdht.org, which needs outbound UDP to those hosts — on a network that blocks it, mdk run stops after Starting worker … and mdk status reports the Kernel unreachable with ERR_MDK_STATUS_TIMEOUT. Private DHT covers multi-host setups.

Scaffold the Worker, the app and the dashboard

3.1 Create the Worker

mdk create worker rig

This writes workers/rig/, a Worker Plugin with one mock device, adds it to mdk.yaml under spec.workers, and installs it.

3.2 Create the app

mdk create app ops

This writes apps/ops/ with a route table, a summary controller and a starter UI, adds it to mdk.yaml under spec.apps, and builds the UI into apps/ops/ui/.

Expected output ends with:

✔ Scaffolded app ops → /home/you/my-site/apps/ops
  package ops
  ✔ Built the starter UI → ui/index.js (source in ui-src/)
  ✔ Added to mdk.yaml under spec.apps
  mdk.yaml spec.rbac role app-ops grants app:ops the Kernel access it requests

That last line is the app's own access policy: mdk create app writes an app-ops role from the app's mdk-app.yaml permissions and binds it to the app:ops service account, so the app can reach the Kernel once the stack runs.

3.3 Create the dashboard

mdk create dashboard

This copies the MDK dashboard into apps/dashboard/, points its dev server at the Gateway port from mdk.yaml, and installs it.

Run it

4.1 Start the stack

mdk rbac init
mdk run

mdk rbac init writes the roles and bindings spec.rbac needs, binding the CLI's own account to an admin role and granting each app the Kernel access its manifest requests. Without them mdk run refuses to boot a Kernel nobody could administer.

mdk run boots the Kernel, the rig Worker with its mock miner, the ops app, and the Gateway in one process. Expected output ends with:

✔ gateway listening on http://localhost:3847

Stack running. Press Ctrl+C to stop.

4.2 Create a user

No route is open, so the dashboard and the HTTP call below both need a signed-in user. In a second terminal, from ~/my-site, with the Kernel still running:

mdk user add ops@example.com --admin   # prompts for a password

--admin binds the user to a role holding wildcards, which is what makes every route below answer. Creating the first operator covers what the flag writes, the other ways to sign in, and the failures to expect.

4.3 Start the dashboard

From that same terminal:

mdk run dashboard

The dashboard dev server prints its URL, http://localhost:3030/ unless that port is taken.

4.4 Open the app

Open http://localhost:3030/ in a browser and sign in as ops@example.com with the password you just set. Home shows an ops card with Workers 1 and Devices 1. Select ops in the sidebar to open the app's own page.

The same data is one HTTP call away. From any terminal:

TOKEN=$(curl -s -X POST http://localhost:3847/auth/login \
  -H 'content-type: application/json' \
  -d '{"username":"ops@example.com","password":"<the password you set>"}' | jq -er .accessToken)

curl -s -H "Authorization: Bearer $TOKEN" http://localhost:3847/apps/ops/api/ops/summary

Expected output:

{"ok":true,"kernelConnected":true,"workerCount":1,"deviceCount":1,"workers":[{"workerId":"rig","devices":1}]}

Change the page

The page's source is apps/ops/ui-src/. Change the line under the heading in apps/ops/ui-src/pages/overview-page.tsx, then rebuild:

cd ~/my-site/apps/ops
npm run build:ui

Reload the ops page in the browser to see the change. npm run dev:ui rebuilds on every save instead.

What just happened

  1. mdk.yaml is the stack spec. Each mdk create command added its component to it, and mdk run boots what it lists.
  2. The Worker runs its mock miner (mock: true in mdk.yaml) and registers its device with the Kernel.
  3. The app declares its routes in apps/ops/plugin/mdk-plugin.json. Its summary controller asks the Kernel which Workers and devices are connected.
  4. The Gateway signs users in at /auth/login, checks every call against the spec.rbac rules bound to the signed-in user, serves the app's routes under /apps/ops/, and forwards each allowed call to the app.
  5. The dashboard serves the app's built UI from apps/ops/ui/, and its dev server proxies /apps/* to the Gateway, so the page fetches from the same origin.

Cleanup

Press Ctrl+C in both terminals. mdk run stops every component it started.

To reset the stack's state, delete ~/my-site/.mdk/. To remove the project, delete ~/my-site/.

Next steps

On this page