Cube
Semantic layer and headless BI for building data apps
Cube is an open source headless business intelligence platform that provides a semantic modeling layer, caching, and APIs for building data applications and dashboards. It exposes metrics over REST, GraphQL, and SQL so any frontend can consume consistent data definitions.
Key features
- Reusable semantic data models
- REST, GraphQL and SQL APIs
- Pre-aggregation caching
- Multi-tenant data access
Pros & cons
Strengths
- Consistent metrics across tools
- Strong API surface
Trade-offs
- Requires data modeling effort
- No bundled dashboard UI
Cube replaces
Last reviewed Aug 26, 2026 · 790 words
Cube ships no dashboard. Install it expecting charts and you will find a developer Playground on port 4000 and nothing else, because Cube sits between your database and whatever draws the charts. What it gives you is a single definition of "active users" or "monthly revenue" that Grafana, Metabase, Superset and your own app all read identically, plus a cache that turns a 40-second aggregate over a big Postgres table into a 200 ms answer. For one person with one dashboard tool it is a layer too many. For a homelab or small company where 3 tools disagree about last month's numbers, it is the fix.
Where it sits in a self-hoster's stack
The shape is database, then Cube, then consumers. Cube connects to Postgres, MySQL, ClickHouse, BigQuery, DuckDB and about 30 other sources with a driver each. It reads a data model you write, answers queries against that model, and caches the results. Nothing about your database changes; Cube is read-only against it, and the sensible setup is a database user that can only SELECT. It is not an ETL tool, not a warehouse, and not a place to store anything but its own cache, which is why the 512 MB minimum is realistic for the API process.
The data model is the work, and it is small YAML
A cube is a table plus the measures and dimensions you allow people to ask about:
cubes:
- name: orders
sql_table: public.orders
measures:
- name: count
type: count
- name: revenue
sql: amount
type: sum
dimensions:
- name: status
sql: status
type: string
- name: created_at
sql: created_at
type: time
Joins between cubes, calculated measures, and access rules go in the same files under model/. The "data modelling effort" the catalogue warns about is real but bounded: a 10-table schema is an afternoon. The return is that "revenue by status by month" is now one query every tool phrases the same way, and a change to the definition of revenue happens in one file rather than in 6 saved queries.
The SQL API is the trick that makes existing tools work
Cube exposes REST at /cubejs-api/v1/load, GraphQL, and a SQL API on port 15432 that speaks the Postgres wire protocol. That last one matters most for self-hosters: add a Postgres data source in Grafana or Metabase pointing at Cube instead of the real database, authenticate with CUBEJS_SQL_USER and CUBEJS_SQL_PASSWORD, and every dashboard now goes through the semantic layer and the cache without anyone learning a new API. Cubes appear as tables, measures as columns; SELECT MEASURE(revenue), status FROM orders GROUP BY 2 is a valid query. The dashboards category covers the front-end choices; any of them with a Postgres connector works here.
Compose, and the dev-mode trap
services:
cube:
image: cubejs/cube:latest
ports:
- "4000:4000"
- "15432:15432"
environment:
- CUBEJS_DEV_MODE=true
- CUBEJS_DB_TYPE=postgres
- CUBEJS_DB_HOST=db.internal
- CUBEJS_DB_NAME=shop
- CUBEJS_DB_USER=cube_ro
- CUBEJS_DB_PASS=change-me
- CUBEJS_API_SECRET=a-long-random-string
volumes:
- ./model:/cube/conf/model
CUBEJS_DEV_MODE=true gives you the Playground, hot reload of the model, and an embedded Cube Store; it also skips authentication on the API. That is fine on a LAN for a week and wrong forever after. Set it to false for anything reachable, at which point API calls need a JWT signed with CUBEJS_API_SECRET, and the Playground disappears, which is by design.
Pre-aggregations are the cache, and they need their own process
Cube's speed comes from pre-aggregations: rollups it builds ahead of time and stores in Cube Store, its own Rust columnar engine. You declare them per cube (pre_aggregations: with measures, dimensions and a time granularity), and Cube refreshes them on a schedule. In production Cube Store runs as a separate cubejs/cubestore container with a persistent volume, and it is the part that wants RAM as your rollups grow; 1 to 2 GB is a reasonable floor once you use them seriously. Without pre-aggregations Cube is a pass-through with a short-lived query cache, which is still useful but is not what the star count is for.
What I'd do
Run Cube only once a second consumer of the same numbers exists. When it does: read-only database user, model in git, dev mode on for a week and then off, Cube Store as its own container with a volume, and Grafana or Metabase repointed at port 15432 so nobody has to change how they work. Start with 2 or 3 pre-aggregations on the queries that are actually slow, not the whole schema. It is a developer tool that pays for itself the first time two dashboards would otherwise have argued.
Similar dashboards & startpages apps
Portainer CE
Dashboards & StartpagesLightweight management UI for Docker and Kubernetes
Replaces Rancher, Docker Desktop
CasaOS
Dashboards & StartpagesSimple, easy-to-use home cloud system and dashboard
Replaces Google homepage
Glance
Dashboards & StartpagesSelf-hosted feeds and info dashboard
Replaces Heimdall
Web Check
Dashboards & StartpagesAll-in-one OSINT tool for analysing any website
Homepage
Dashboards & StartpagesFast, file-configured application dashboard
Replaces Heimdall
Dashy
Dashboards & StartpagesFeature-rich personal dashboard for self-hosters
Replaces Heimdall