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:
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:
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.
-
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.ymlcarries Traefik docker-provider labels (and anauthelia@dockermiddleware you can leave off). -
Turn on built-in auth. Set
ASMLTR_AUTH=onand a persistentASMLTR_AUTH_SECRETon 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. -
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. -
Set the hostname. Set
ASMLTR_INSIGHTS_HOST(or edit the router'sHost(...)rule in the compose labels). -
TLS. Let the proxy issue the cert (Traefik
certresolver, Caddy auto-HTTPS, Certbot for nginx). -
Verify.
dig +short <host>resolves;curl -sI https://<host>/api/health401s (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 andRemote-User. - External authenticator as the sole edge gate. Put the authenticator's forward-auth on the router and set
ASMLTR_AUTH=offso the built-in gate stands down (break-glass) and nginx forwards the edge'sX-Remote-User. Restrict access to the single user in that tool's allow-list (e.g. an Autheliaaccess_controlrule withtwo_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_LISTENto a private interface the proxy can reach — e.g. the docker-bridge gateway172.18.0.1:8091— not the public NIC; - set
ASMLTR_UPSTREAM_HOST=127.0.0.1so nginx reaches the host services on loopback; - point the proxy at
http://172.18.0.1:8091via 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.