How to share a Voilà dashboard
Voilà is a clean idea: take a notebook, hide the code cells, and serve the outputs and widgets as a dashboard. The thing to understand before you plan any sharing around it is how it does that, because the mechanism sets every constraint that follows.
Voilà runs a kernel per viewer
When someone opens your Voilà dashboard, the server starts a Jupyter kernel, executes the notebook, and streams widget state to that browser over a websocket. Move a slider, the kernel re-runs the callback, the output updates.
Two consequences, both load-bearing:
There is no static export. The interactivity is the kernel. Ask for a self-contained file and you are asking for nbconvert, which is a different tool doing a different job.
Concurrency costs memory, not bandwidth. Ten simultaneous readers means ten kernels. If the notebook loads a 2 GB DataFrame at the top, that is 20 GB. Most Voilà deployments discover their real capacity limit this way, usually during a demo.
Hosting it properly
If the widgets are genuinely the point — a parameter explorer, a simulation people drive — then host it and accept the shape:
- A container running
voilabehind a reverse proxy with authentication, on whatever you already operate. - JupyterHub, if you have it. It already manages per-user kernels, which is exactly the problem Voilà creates, and it already has auth.
- Binder, for public demos with no data sensitivity, at the cost of a slow cold start and no guarantees.
Size the instance by peak concurrent readers, not by average, and load data lazily inside callbacks rather than at import time so an idle viewer is cheap.
The far more common case
Somebody wants to see the analysis. They will not move the slider. They want to read the conclusion, look at two charts, and ask about one of them.
Hosting a kernel farm for that reader is a poor trade, and there is a one-command alternative.
Execute and publish
jupyter nbconvert --to html --execute --no-input \
--HTMLExporter.theme=light analysis.ipynb --output report.html
--execute runs it fresh so the outputs match the current data, --no-input
hides the code cells the way Voilà does, and the result is one file.
The important nuance: client-side interactivity survives this. Plotly, Bokeh, Altair and folium render as JavaScript that runs in the reader's browser with no kernel behind it, so zoom, hover and legend toggles all keep working in the static file. What you lose is precisely the ipywidgets controls — sliders, dropdowns, buttons — that need Python to respond. If your dashboard's interactivity is mostly charts rather than widgets, the static render loses almost nothing.
Then publish, and keep publishing to one id:
curl -fsS -X PATCH "https://commareports.com/api/v1/reports/$REPORT_ID" \
-H "Authorization: Bearer $COMMA_API_TOKEN" \
-H "Content-Type: application/json" \
-d "$(jq -n --rawfile html report.html \
--arg title "Cohort analysis — $(date +%F)" '{title: $title, html: $html}')"
A routine runs the execute-and-publish loop on a schedule, which covers the "the dashboard should be current" requirement that usually motivates hosting Voilà in the first place.
What the published version has that the hosted one does not
A record. Voilà always shows now. Every previous state is gone. Published revisions keep every run, so "what did this look like before the pipeline change" is a diff.
Somewhere to put the discussion. Anchored threads land on a specific figure — "this cohort excludes trials, that's why it looks flat" — and stay attached as revisions accumulate.
No uptime. No kernel, no memory ceiling, no cold start, no reader waiting on a container.
Access is a setting on the report: private, team, domain-gated or link.
Choosing
Widgets that people actually drive → host Voilà, size for concurrency, put auth in front. Charts and narrative that people read → execute, publish, and put the kernel budget somewhere it earns more.
Try it
Comma is free — unlimited reports, unlimited commenters, unlimited revision history.