Skip to content

Deploying the web dashboard

The insights/dashboard is a Vue 3 observability GUI — live sessions, a cross-surface timeline, usage, and the trust Access page. It's an optional part of an install: the asmltr CLI/TUI already gives full local monitoring. Deploy the dashboard when you want a browser view.

It ships as a static Vite build served by nginx. That nginx also reverse-proxies the backend so tokens never reach the browser:

Path Proxies to Purpose
/api, /socket.io collector :3017 read telemetry (REST + websocket)
/api/control collector :3017 control plane (kill / stop / send-keys) — uses the stronger control bearer
/manager connector manager :3024 connector control plane (instances, config)
/trust, /v2 core :3023 trust/Access API and session inject/abort (takeover)

Each service bearer is injected server-side by nginx (via envsubst at container start), and the authenticator-resolved user is forwarded as X-Remote-User for audit. Reference: insights/docker-compose.yml and insights/dashboard/nginx.conf.template.

Never expose it publicly without authentication

Because nginx proxies the control plane — the connector manager (/manager) and the core trust/Access API (/trust) — exposing the dashboard to the internet without authentication hands anyone your control plane. If it is reachable publicly, access MUST be gated to the specific user(s) — turn on asmltr's built-in auth (ASMLTR_AUTH=on; see Option B below), or put an external authenticator in front. If you can't set that up, deploy local-only (over an SSH tunnel) instead.

Build the SPA

Both deployment options need the static build:

(cd insights/dashboard && npm install && npm run build)

Environment knobs

The compose file and nginx template read these (all optional, with sane defaults):

Variable Meaning Default
ASMLTR_INSIGHTS_HOST Traefik router hostname (the Host rule) insights.example.com
ASMLTR_NGINX_LISTEN nginx listen directive 80
ASMLTR_UPSTREAM_HOST where nginx reaches the host services host.docker.internal
ASMLTR_INSIGHTS_TOKEN collector read bearer (must match the collector's token)
ASMLTR_MANAGER_TOKEN connector-manager bearer (control plane)
ASMLTR_INSIGHTS_CONTROL_TOKEN collector control-plane bearer (kill/stop/send-keys)

Set the token variables only if you configured tokens on the collector/manager. The read and control tokens must match what the collector expects.

Option A — local-only (no domain, no proxy)

Simplest and always available: serve the SPA bound to loopback and tunnel in.

ASMLTR_NGINX_LISTEN=127.0.0.1:8091 ASMLTR_UPSTREAM_HOST=127.0.0.1 \
  docker compose -f insights/docker-compose.yml up -d --build

Then from your workstation:

ssh -L 8091:127.0.0.1:8091 <server>
# open http://localhost:8091

Option B — public, behind a reverse proxy (built-in auth)

Expose the dashboard on a hostname with a reverse proxy for TLS, and let asmltr's built-in auth gate access — no external authenticator required. This is the recommended way to run it publicly.

The dashboard's own nginx already enforces this: with ASMLTR_AUTH=on, every proxied backend call (/api, /v2, /manager, /trust, /socket.io) runs an auth_request subrequest to the core's GET /v2/auth/verify, so an unauthenticated browser gets asmltr's login / first-run screen instead of the control plane. The session-resolved user is forwarded as X-Remote-User for audit. Login supports a password plus TOTP two-factor and one-time recovery codes.

  1. Reverse proxy for TLS + routing. A reverse proxy (Traefik, Caddy, or nginx) terminates TLS and routes the hostname to the dashboard container. No forward-auth middleware is needed — the auth gate lives inside the dashboard's nginx. The shipped insights/docker-compose.yml carries Traefik docker-provider labels (and an authelia@docker middleware you can leave off).

  2. Turn on built-in auth. Set ASMLTR_AUTH=on and a persistent ASMLTR_AUTH_SECRET on the core, restart it, then open the dashboard and complete the first-run screen to create the admin account. Enroll TOTP under Settings → Security for two-factor. See AUTH.md for all knobs.

  3. DNS. Point your hostname (e.g. insights.example.com) at the server's public IP. If DNS is behind a proxying CDN, use DNS-only or an origin cert so the ACME/TLS challenge can complete.

  4. Set the hostname. Set ASMLTR_INSIGHTS_HOST (or edit the router's Host(...) rule in the compose labels).

  5. TLS. Let the proxy issue the cert (Traefik certresolver, Caddy auto-HTTPS, Certbot for nginx).

  6. Verify. dig +short <host> resolves; curl -sI https://<host>/api/health 401s (not the data) when unauthenticated, and the SPA shows the login / first-run screen; after logging in as the admin (with 2FA) you reach the dashboard.

Optional — front it with an external authenticator (SSO / hardware keys)

The built-in auth covers password + TOTP. Reach for an external authenticator (Authelia, oauth2-proxy, Authentik, Cloudflare Access) only when you need something the built-in doesn't do yet — the common case is hardware FIDO2 / YubiKey / WebAuthn, or folding the dashboard into an existing SSO estate.

If you do, make the external authenticator the single front door — don't stack two independent logins, or the user authenticates twice. Pick one:

  • External IdP as identity source (recommended for SSO). Keep asmltr's built-in login as the gate, but let people sign into it through the external provider using asmltr's OIDC-client login (ASMLTR_OIDC_<PROVIDER>_ID/_SECRET; an existing Authelia works as the provider). One login, external identities, built-in auth still owns the session and Remote-User.
  • External authenticator as the sole edge gate. Put the authenticator's forward-auth on the router and set ASMLTR_AUTH=off so the built-in gate stands down (break-glass) and nginx forwards the edge's X-Remote-User. Restrict access to the single user in that tool's allow-list (e.g. an Authelia access_control rule with two_factor, above any catch-all).

Either way, don't run an external forward-auth gate and ASMLTR_AUTH=on as two parallel gates.

The network reachability gotcha

The host services bind 127.0.0.1, so a reverse-proxy container cannot reach them directly. Two ways around it:

Recommended: host-network dashboard

When the host services are 127.0.0.1-only, run the dashboard with network_mode: host and:

  • set ASMLTR_NGINX_LISTEN to a private interface the proxy can reach — e.g. the docker-bridge gateway 172.18.0.1:8091not the public NIC;
  • set ASMLTR_UPSTREAM_HOST=127.0.0.1 so nginx reaches the host services on loopback;
  • point the proxy at http://172.18.0.1:8091 via a file/dynamic route (don't rely on the docker-provider labels in this mode).

The alternative is host.docker.internal, which works on Docker Desktop, and on Linux only if the host services also listen on the bridge. In that case the shipped compose's traefik-network + labels model works as-is.

Updating

When the dashboard is deployed, rebuild it after pulling a new asmltr version so the GUI picks up the update:

(cd insights/dashboard && npm install)
docker compose -f insights/docker-compose.yml up -d --build   # + any -f override / env vars you deployed with

If it's public, confirm afterward that an unauthenticated request still hits the login screen (backend paths still 401) — that auth didn't regress.

Let the updater rebuild the local-only dashboard

scripts/update.js rebuilds the dashboard on every asmltr update, but it only knows about the base insights/docker-compose.yml unless you tell it otherwise. That base file joins the external traefik-network; on a local-only box it fails with network traefik-network declared as external, but could not be found, so the rebuild fails each update & the GUI silently lags a version.

Save your local-only compose as insights/docker-compose.<name>.yml (e.g. insights/docker-compose.local.yml). .gitignore matches insights/docker-compose.*.yml, so git reset --hard during an update never touches it, & scripts/update.js scans insights/ for a docker-compose.*.yml override & prefers it over the base Traefik compose. A minimal host-networked file:

services:
  asmltr-insights-dashboard:
    build: ./dashboard
    container_name: asmltr-insights-dashboard
    network_mode: host
    environment:
      - NGINX_LISTEN=127.0.0.1:8091
      - ASMLTR_UPSTREAM_HOST=127.0.0.1

If you first ran the dashboard from a compose in another directory, run docker rm -f asmltr-insights-dashboard once so the new compose project can own the container name.