Deployment Profiles
Overview
Freeboard supports three operational profiles. Choose one per environment and enforce it through policy/env configuration.
Profile 1: Full App (Interactive)
Use for authoring and operations teams.
- Login enabled
- Admin console enabled
- Dashboard editing enabled for
editor/admin - Sharing and collaboration enabled
Recommended defaults:
EXECUTION_MODE=safeAUTH_REGISTRATION_MODE=invite(ordisabled)AUTH_NONADMIN_CAN_PUBLISH=falseunless explicitly needed
Profile 2: Local-First (Lite)
A single static build (FREEBOARD_STATIC=true) that runs with no server: it persists the dashboard locally and is editable in the browser. Use for a lightweight self-hosted/offline/embeddable dashboard, docs/demos, or an embed target.
- Built with
FREEBOARD_STATIC=true; no server, no/graphqltraffic. - The dashboard is locally editable and persisted to
localStorageas a single portable v1DashboardDocument. Import/Export to a file also work. - No login/admin, no sharing/share-tokens/collaborators, and no server saved-dashboards picker — those affordances are absent, not merely hidden.
- Datasource support is bounded by the matrix below (Clock/Static/direct-HTTP; streaming types require the server).
Contract:
- Persistence is local to the browser/origin; it is not a shared or backed-up store.
- Do not rely on static mode for protected private dashboards or server-enforced access control.
Immutable display (read-only)
The same static build serves a view-only board by loading it with the ?readonly query parameter (?readonly, ?readonly=1, or ?readonly=true). Use it for a kiosk/wallboard URL or a read-only embed.
- Load
https://your-host/?readonly=1(or set it as the iframesrc). A document injected by either channel below renders, but the entire edit surface is absent — no edit toolbar, no edit/add/delete/drag/resize, no Save. This is enforced (the controls are not rendered and the grid is not interactive), not merely hidden. - It is a runtime switch on the same static build — there is no separate artifact, so one deployment can serve both editable (
/) and read-only (/?readonly=1) URLs. - Datasources still run per the matrix below — the board is live but immutable.
- Honoured only in a static build (
FREEBOARD_STATIC=true); in the full app, edit rights come from authentication, not the URL. - This is a display convenience, not an access control: a viewer who edits the URL can drop
?readonly. For an embed, the embedder controls the iframesrc; for true private/protected dashboards use the full app's auth and visibility. - Read-only locks editing, not what is shown: a read-only board still accepts an injected document (see Embedding), so an embedder permitted by the origin allowlist can replace the displayed content. Read-only removes the edit/persist surface; it does not pin the document.
Datasource feasibility (static build)
A static build has no Freeboard server/gateway, so datasource support falls into three tiers:
| Datasource | Static build | Why |
|---|---|---|
| Clock | Works | Pure client-side; no network. |
| Static | Works | Inline value; no network. |
| HTTP | Direct only | Fetches directly from the browser. Subject to the target endpoint's CORS policy, and credential profiles are unavailable (the secret-holding gateway is server-only). |
| SSE / WebSocket / MQTT | Unavailable | Gateway-only streaming; require the Freeboard server. Not registered in a static build (cannot be added); existing documents referencing them load inert. |
| HTTP via gateway | Unavailable | The gateway is server-only; static HTTP is always direct (no gateway toggle, no session-token mint). |
CORS is a real limitation of direct HTTP, not a bug — point HTTP datasources at endpoints that permit cross-origin requests.
Embedding (Lite)
A static build is an embed target. Host it (the docs site already ships the static build via site:copy-demo) and inject a portable v1 DashboardDocument through one of two channels:
- Cross-origin —
postMessage(recommended for iframes). The embedder posts a{ type: "freeboard:load-document", document }message to the iframe; the document is validated and migrated before it loads, and rejected without side effects if invalid. Restrict who may inject with the build-timeVITE_FREEBOARD_EMBED_ALLOWED_ORIGINS(comma-separated origins; empty or*accepts any — the document is still validated and runs insafeexecution mode). - Same-origin —
localStorage. When the host serves the build from the same origin, seed thefreeboard:dashboardkey with the document JSON before load. This is subject to browser storage partitioning across origins, which is whypostMessageis the cross-origin channel.
The local-edit contract (above) and the datasource matrix apply to embedded instances too.
Minimal iframe example (any portable v1 document works; packages/core/test/fixtures/full.json is a ready sample):
<iframe
id="board"
src="https://your-host/freeboard/"
style="width: 100%; height: 600px; border: 0"
></iframe>
<script type="module">
const board = document.getElementById("board");
const dashboard = await fetch("./full.json").then((r) => r.json());
board.addEventListener("load", () => {
board.contentWindow.postMessage(
{ type: "freeboard:load-document", document: dashboard },
"https://your-host",
);
});
</script>Profile 3: Kiosk Appliance (Viewer-Only Runtime)
Use for wallboards, signage, and IoT/device interfaces.
User/access contract:
- Device opens one dashboard URL in kiosk browser mode
- Device account should be viewer-only (or link-share/public URL when explicitly intended)
- No admin/editor credentials on kiosk device
Security contract:
- Default to
safeexecution mode - For control interfaces, keep dashboard visibility
private - Use
link/publiconly for low-sensitivity signage - On device compromise: rotate share token or deactivate kiosk account immediately
Container image architecture contract:
- Prefer 64-bit Raspberry Pi OS and mainline images (
latest,v*,sha-*). - 32-bit Raspberry Pi OS uses legacy
-armv7image tags. - Legacy
-armv7tags are intentionally separated to avoid blocking mainline runtime upgrades. arm/v6is intentionally unsupported.
Architecture verification command:
docker buildx imagetools inspect node:24.13.1-alpineReference links:
- Docker Raspberry Pi OS install guidance:
- Docker Engine 29 release notes (32-bit Pi context):
- Node Docker image upstream (platform support varies per tag/variant):
- Node official tags (including legacy-friendly LTS lines):
Kiosk Provisioning Subprofiles (Ansible)
Kiosk deployments include three provisioning subprofiles:
player_only(default): display + player service onlyappliance_with_runtime: adds container runtime setupappliance_with_runtime_and_boot_tuning: adds runtime + boot/splash tuning
Use the least invasive profile that meets your target environment.
URL and access guidance
- Private authenticated dashboard:
/:id - Public dashboard route:
/p/:id - Link-share dashboard route:
/s/:shareToken
Kiosk Decision Tree
- Need only dashboard playback on an already-managed host?
- Use
player_only.
- Use
- Need this host to also run local container runtime dependencies?
- Use
appliance_with_runtime.
- Use
- Need boot splash and kernel boot tuning on appliance-class hardware?
- Use
appliance_with_runtime_and_boot_tuning(only after canary validation).
- Use
Operational checklist
- Confirm profile for environment (
full,static, orkiosk). - For kiosk: choose provisioning subprofile (
player_only,appliance_with_runtime, orappliance_with_runtime_and_boot_tuning). - Apply matching env/policy settings and role/visibility constraints.
- Validate behavior (auth role, dashboard visibility, and kiosk URL access).
- Record profile and rollback path in deployment runbook.