Skip to content

bino serve

bino serve starts a production HTTP server that serves a LiveReportArtefact as an interactive web application. Unlike preview, serve does not watch for file changes and renders on-demand per request.

bino serve --live <name> [flags]

Required flags:

  • --live - name of the LiveReportArtefact to serve (required).

Common flags:

  • --work-dir - report bundle directory (default: .).
  • --port - HTTP port (default: 8080).
  • --addr - full listen address (overrides --port, e.g. 0.0.0.0:8080).
  • --log-sql - log executed SQL queries.
  • --data-mode - dataset delivery: inline (default) embeds the data gzip+base64 in the HTML, url fetches it over HTTP from the serve process.

With url, the browser loads each DataSet and DataSource with its own request to /__bino/data/. The response is sent with Cache-Control: private, no-store. The data is available while its page is among the 100 most recently used pages of the serve process. Behind several replicas the request must reach the process that rendered the page. The data URL does not carry the query parameters of the route, only a hash of the content. So a proxy that sets a parameter for the viewer does not restrict it: whoever knows the URL of a body gets that body. inline has no such limits, but the page is larger.

  1. Loads configuration - reads all manifests from the work directory.
  2. Validates routes - ensures the selected LiveReportArtefact has a root route and all referenced ReportArtefacts exist.
  3. Serves on-demand - renders reports when requested, using caches for efficiency.
  4. Nvigation - enables smooth client-side navigation without full page reloads.

A served page carries the rows of the DataSets and DataSources that its rendered components bind with dataset. A DataSource is bound as $name. This includes components pulled in with ref and components inside cards, grids and tree nodes. It is the same for both --data-mode values. A route with several LayoutPages carries the data of all of them. Other DataSets and DataSources of the project are not sent to that page.

The value and label columns of a DataSet used for the options of a select parameter are also sent to the page.

A component filter runs in the viewer’s browser. Every row of the component’s dataset reaches the viewer. The filter only decides which rows are drawn.

To restrict the rows a page carries, filter in the DataSet SQL with the route’s query parameter, and bind the component to that DataSet:

---
apiVersion: bino.bi/v1alpha1
kind: DataSet
metadata:
  name: region_sales
spec:
  query: |
    SELECT * FROM sales
    WHERE region = '${REGION}'
  dependencies:
    - sales
---
apiVersion: bino.bi/v1alpha1
kind: LayoutPage
metadata:
  name: region-page
spec:
  children:
    - kind: Table
      spec:
        dataset: region_sales
---
apiVersion: bino.bi/v1alpha1
kind: LiveReportArtefact
metadata:
  name: sales-dashboard
spec:
  title: "Sales Dashboard"
  routes:
    "/":
      layoutPages: region-page
      queryParams:
        - name: REGION

With ?REGION=EU, the page carries only the EU rows of sales. The LayoutPage below draws the same rows, but its Table binds $sales directly, so every row of sales reaches the browser:

apiVersion: bino.bi/v1alpha1
kind: LayoutPage
metadata:
  name: region-page
spec:
  children:
    - kind: Table
      spec:
        dataset: $sales
        filter: "region = '${REGION}'"

Filtering in the DataSet limits the rows per viewer only when a layer in front of bino serve, for example a proxy, sets the parameter and the viewer cannot change it.

See Query parameter substitution for how parameters are substituted.

Each route in a LiveReportArtefact can define its own query parameters that are substituted into report documents using ${VAR} syntax. This allows dynamic filtering and customization of reports on a per-route basis.

Query parameters support different input types in the serve UI sidebar:

  • string (default) - text input
  • number - numeric input with optional min/max/step
  • number_range - dual range slider for range filtering
  • select - dropdown with static items or options from a DataSet
  • date - date picker
  • date_time - date and time picker

Parameters are required by default. To make a parameter optional, either:

  • Set a default value, or
  • Set optional: true (the parameter will be empty if not provided)

If a required parameter is missing, the server returns HTTP 400 with a JSON error listing the missing parameters.

apiVersion: bino.bi/v1alpha1
kind: LiveReportArtefact
metadata:
  name: sales-dashboard
spec:
  title: "Sales Dashboard"
  routes:
    "/":
      artefact: overview-report
    "/region":
      artefact: region-report
      title: "Regional Sales"
      queryParams:
        - name: REGION
          type: select
          default: "all"
          description: "Filter by region (has default)"
          options:
            items:
              - value: "all"
                label: "All Regions"
              - value: "EU"
                label: "Europe"
              - value: "US"
                label: "United States"
        - name: YEAR
          type: number
          description: "Year to display (required)"
          options:
            min: 2000
            max: 2030
        - name: NOTES
          optional: true
          description: "Optional notes filter"

Access with: http://localhost:8080/region?REGION=EU&YEAR=2024

See LiveReportArtefact reference for full documentation of parameter types and options.

Set default values for serve flags in your project’s bino.toml:

[serve.args]
port = 8080
live = "sales-dashboard"
log-sql = true

Set environment variables for serve in bino.toml:

[serve.env]
BNR_MAX_QUERY_ROWS = "100000"
DATABASE_URL = "postgres://localhost/prod"

Serve a live report artefact:

bino serve --live sales-dashboard

Serve on a specific port with SQL logging:

bino serve --live sales-dashboard --port 3000 --log-sql

Serve bound to all interfaces (for Docker/Kubernetes):

bino serve --live sales-dashboard --addr 0.0.0.0:8080

The default bind is 127.0.0.1, which is unreachable from outside a container, so --addr is mandatory there. It is also the only serve flag that cannot be set from bino.toml - pass it on the command line.

See Recipe: Deploy a live dashboard for Compose and Kubernetes manifests, and Running bino in Docker for the image itself.

When the served LiveReportArtefact defines spec.pwa, bino serve makes the application installable as a Progressive Web App:

  • GET /manifest.webmanifest - the web app manifest, generated from spec.pwa (names, colors, display mode, icons). Titled non-root routes additionally become manifest shortcuts - the jump list shown when long-pressing or right-clicking the installed app’s icon.
  • GET /sw.js - the service worker.
  • Icon files come from the referenced Asset manifests and are served at assets/files/<name>.
  • Every page gets the manifest link and the service-worker registration injected into its <head> - plus a theme-color meta tag when themeColor is set, and an apple-touch-icon link when at least one icon has purpose any.

The two paths are reserved: a route at /manifest.webmanifest or /sw.js fails validation, even when spec.pwa is not set. Without spec.pwa, serving is unchanged - no extra tags or routes.

The injected tags use relative URLs. When accessing bino serve directly, install from a single-segment route such as / or /sales: on a nested route like /reports/q1 the browser resolves the relative links into the route’s subdirectory, where they are not found. Behind a reverse proxy that injects a <base> tag (as bino cloud does) the links resolve on every route.

Browsers only offer installation on a secure context: HTTPS or localhost. http://localhost:8080 is enough to try it out during development; a deployed dashboard must sit behind TLS - see Recipe: Deploy a live dashboard. All generated URLs are relative, so the app is installable whether it is served at the domain root or behind a path prefix.

The service worker caches the static engine assets (scripts, styles, fonts) so an installed app starts quickly. Report data is never cached: rendering a route always requires connectivity to the serve process. When the network is unreachable, the app shows an offline fallback page instead of stale data.

The browser re-fetches sw.js in the background when the app is opened. A changed worker installs, activates immediately, and takes control of the running app - no relaunch needed - dropping caches from older engine versions, so updated engine assets roll out without user action.

GET /healthz returns 200 with the body ok and requires no authentication. It reports that the process is up and serving HTTP; it does not verify database connectivity or that any route renders. Use it for container health checks and Kubernetes probes.

curl -fsS http://localhost:8080/healthz
Feature bino preview bino serve
File watching ✓ Yes ✗ No
Hot reload ✓ Yes ✗ No
Query param substitution ✗ No ✓ Yes
Production-ready ✗ No ✓ Yes
Navigation ✗ No ✓ Yes
Default port 45678 8080

Use preview during development for fast iteration. Use serve for production deployments.