Back to Blog
Apache SupersetBusiness IntelligenceData VisualizationDashboardsSQL LabRow-Level SecuritySemantic LayerCeleryAnalyticsOpen Source

Apache Superset in Production — Dashboards, Semantic Layer, Row-Level Security, and Deployment

A practical guide to running Apache Superset, the open-source BI platform built on Flask, React, and SQLAlchemy, as a real production system rather than a demo: getting a working instance up with the project's Docker Compose setup that brings up the web app, a Postgres metadata database, Redis, and the Celery worker and beat pre-wired — cloning the repo with a shallow clone, fetching tags, checking out the 6.0.0 release tag, and logging in at port 8088 with the default admin credentials while SUPERSET_LOAD_EXAMPLES controls example data; the pip-based install and CLI bootstrap with FLASK_APP and SUPERSET_CONFIG_PATH, superset db upgrade, superset fab create-admin, and superset init, plus the SQLALCHEMY_DATABASE_URI metadata database that every clustered node must share and that cannot be SQLite; connecting analytics databases as SQLAlchemy URIs so queries push down to engines like Postgres, ClickHouse, or Trino; the deliberately thin dataset-based semantic layer with physical versus virtual datasets, Jinja-templated SQL, metrics as aggregate expressions and calculated columns as row-level expressions, and the warning not to embed GROUP BY or ORDER BY in a virtual dataset because Superset wraps it as a subquery; SQL Lab as both the SQL IDE and the debugger behind every chart via View query and Run in SQL Lab; Row-Level Security for multi-tenancy with Regular versus Base filters, the group_key that combines rules with OR within a group and AND across groups, Jinja helpers like current_username(), and the critical caveat that RLS does not sandbox raw SQL Lab access; the Admin, Alpha, and Gamma role hierarchy and why superset init reverts edits to built-in roles; scaling with a Celery worker and a single beat scheduler backed by Redis via CeleryConfig, CELERY_CONFIG, and RESULTS_BACKEND; the DATA_CACHE_CONFIG, CACHE_CONFIG, FILTER_STATE_CACHE_CONFIG, and EXPLORE_FORM_DATA_CACHE_CONFIG caching layers with scheduled cache warmup; and a production checklist covering SECRET_KEY, shared metadata, one-beat-many-workers, layered roles, RLS limits, and Kubernetes for production.

2026-07-19

An Open-Source BI Platform You Actually Own

The business-intelligence market has spent a decade consolidating. Tool after tool got acquired, folded into a suite, and quietly repriced. That churn is exactly why an open, self-hosted option matters: the dashboards, the metric definitions, and the access rules stay in your control rather than a vendor’s roadmap.

Apache Superset is a web application built on Flask, React, and SQLAlchemy. It ships its own SQL IDE (SQL Lab), a drag-and-drop chart builder, a dashboard layout engine with cross-filters, and a role-based access-control system. It graduated as a top-level Apache project and is licensed under Apache 2.0.

This guide is about running Superset seriously — connecting real databases, modelling a semantic layer, isolating tenants with Row-Level Security, and scaling queries with Celery. It is not a click-through of the demo.

Note

This article targets Superset 6.0 (6.0.x at the time of writing). Superset moves quickly and config keys change between majors, so always confirm flags, CLI commands, and API versions against the official documentation for your installed version before copying anything into production.

Getting Running with Docker Compose

The fastest path to a working instance is the project’s Docker Compose setup. It brings up the web app, a Postgres metadata database, Redis, and the Celery worker and beat processes, all pre-wired. Clone the repo, check out a release tag, and start it.

# Shallow-clone the repository
git clone --depth=1 https://github.com/apache/superset.git
cd superset

# A shallow clone has no tags yet — fetch them, then pin a release
git fetch --tags
git checkout tags/6.0.0

# Bring up the image-tag compose file
docker compose -f docker-compose-image-tag.yml up

Once the output settles, open http://localhost:8088 and log in with the default admin / admin credentials. The SUPERSET_LOAD_EXAMPLES variable controls whether the init container loads example dashboards — useful for a first look, unnecessary for a clean deployment.

Note

The project is explicit that Docker Compose is not for production. It runs a set of containers on a single host and cannot meet high-availability requirements. For real deployments the maintainers point you at the Kubernetes install with the official Helm chart. Treat Compose as a local and evaluation tool only.

The pip Install and CLI Bootstrap

Under the hood, Superset is a Python package. Understanding the manual install clarifies what every container is doing and is the basis for a Kubernetes or systemd deployment. Install it alongside the drivers for your metadata store and data sources.

pip install apache-superset psycopg2-binary redis gevent

# Superset is a Flask app; point the CLI at it
export FLASK_APP=superset
export SUPERSET_CONFIG_PATH=/path/to/superset_config.py

# One-time bootstrap sequence
superset db upgrade          # run metadata migrations
superset fab create-admin    # create the first admin user (prompts)
superset init                # sync default roles and permissions

The metadata database — where Superset stores dashboards, datasets, and roles — is configured with SQLALCHEMY_DATABASE_URI in superset_config.py. In any clustered setup every web and worker node must share this one database, and SQLite will not do because of its weak concurrency.

# superset_config.py
SECRET_KEY = "${SUPERSET_SECRET_KEY}"  # set via env; rotate carefully
SQLALCHEMY_DATABASE_URI = "postgresql://superset:${DB_PASSWORD}@db:5432/superset"

Note

superset init re-synchronises the permissions attached to every default role to their canonical values. That means hand-editing a built-in role’s permissions is futile — the next init, often run during an upgrade, will revert it. Create new roles instead of mutating Admin, Alpha, or Gamma.

Connecting Databases

Superset does not store your analytics data. It connects to your existing warehouses and databases through SQLAlchemy and pushes queries down to them. You register a data source once, as a connection string, and every dataset and chart runs against that engine live.

The connection is a standard SQLAlchemy URI, so the driver package must be installed in the Superset image. Superset speaks to dozens of engines this way — Postgres, MySQL, Snowflake, BigQuery, and analytical stores like ClickHouse for real-time analytics or a Trino distributed SQL engine that federates across lakes and databases.

# SQLAlchemy URIs, one per engine (driver must be installed)
postgresql://user:${DB_PASSWORD}@warehouse:5432/analytics
clickhousedb://user:${DB_PASSWORD}@clickhouse:8443/default
trino://user@trino:8080/hive/default

Because queries execute on the source engine, Superset’s performance is largely your warehouse’s performance. A slow dashboard is usually a slow query, which is why picking a columnar analytical engine underneath matters as much as anything you do in the UI.

The Semantic Layer — Datasets, Metrics, Calculated Columns

Superset’s semantic layer is the dataset, and it is deliberately thin. Rather than pushing you to build an elaborate modelling language that locks you in, a dataset is a light abstraction where you declare metrics, add calculated columns, and attach metadata that shapes the UI. Your models stay portable.

Datasets come in two kinds. A physical dataset maps to a table or view; Superset pulls in the schema and column types, and a Sync columns from source action refreshes them when the table changes. A virtual dataset is defined by a SQL query that Superset manages itself — similar to a database view, but versioned in Superset and able to use Jinja templating.

-- A virtual dataset: SQL owned by Superset, not the database
SELECT
  order_date,
  region,
  customer_id,
  amount
FROM orders
WHERE order_date >= '2026-01-01'
-- Avoid GROUP BY / ORDER BY here: Superset wraps this as a
-- subquery and adds its own aggregation, which can double-count.

Metrics and calculated columns are the building blocks on top of a dataset. Both are SQL fragments translated into the query at execution time, so they must be valid for the target engine’s dialect. A metric is an aggregate expression; a calculated column is a row-level expression added to the dataset.

-- Metric (SQL aggregate expression)
SUM(amount)                         -- total_revenue
COUNT(DISTINCT customer_id)         -- unique_customers

-- Calculated column (row-level expression)
CASE WHEN amount > 1000 THEN 'high' ELSE 'standard' END

Note

Superset runs a virtual dataset as a subquery and typically adds a GROUP BY in the outer layer, so embedding your own GROUP BY or ORDER BY is redundant and risks double-aggregating non-additive metrics like COUNT(DISTINCT ...) and averages. Keep virtual datasets as flat SELECTs and let the metric layer do the aggregation.

This dataset-centric model is closer to the transformation-layer thinking of tools like dbt in production than to a heavyweight BI modelling language — define the metric once, reuse it everywhere, and keep the definition where you can version and review it.

SQL Lab and the Explore Workflow

SQL Lab is Superset’s built-in SQL IDE — the place to clean, join, and prepare data before it becomes a chart. Because every visualization ultimately compiles to SQL, SQL Lab doubles as your debugger: any chart’s generated query can be inspected and re-run there.

The loop is tight. In the Explore view, the hamburger menu offers View query to see exactly what Superset sent to the engine, and Run in SQL Lab to open it for iteration. Query results in SQL Lab can then be pivoted straight into the chart builder — the two halves of the product are one continuous workflow.

Row-Level Security for Multi-Tenancy

Row-Level Security (RLS) is how one Superset instance safely serves many tenants. An RLS rule attaches a SQL WHERE clause to specific datasets and subjects, and Superset injects it automatically into every query the semantic layer generates. Users never see the filter; they simply cannot query rows outside it.

There are two filter types, and the distinction matters. A Regular filter applies only when the querying user matches one of the rule’s subjects — use it to grant a role a scoped view. A Base filter applies to everyone except the matching subjects — use it to set a default restriction that privileged roles are exempt from.

# Deny-by-default baseline (Base filter)
clause:      1 = 0          # hides every row...
filter_type: Base
roles:       [Admin]        # ...except for Admin

# Per-tenant grant (Regular filter), Jinja for the current user
clause:      tenant_id = '{{ current_username() }}'
filter_type: Regular
roles:       [Tenant_Analyst]

When several rules apply, the group_key attribute controls how they combine. Filters sharing a group key are joined with OR (any match is enough); filters in different groups — or with no group key — are joined with AND (all must hold). RLS clauses can reference user attributes through Jinja helpers such as {{ current_username() }}.

Note

RLS filters Superset’s own generated queries — charts and dashboards. It does not automatically sandbox raw SQL. A user with SQL Lab access to a database can write arbitrary queries against it, so restrict SQL Lab at the database-permission level for any role that must not bypass RLS, and verify the exact behaviour in your Superset version. Do not treat RLS as your only isolation boundary.

Roles and Access Control

Superset ships a hierarchical role model built on Flask AppBuilder. Three roles carry most deployments, and understanding their boundaries is the difference between a safe rollout and an accidental data leak.

  • Admin. Every permission, including user management and security configuration. Reserve it for platform operators.
  • Alpha. Access to all data sources and can create content, but cannot manage users or alter security settings.
  • Gamma. A limited consumer role — access only to the specific data sources an Admin grants. Combined with RLS, this is the backbone of multi-tenant read access.

Because superset init resets built-in roles to their canonical permissions, model your own access needs as additional roles layered on top of Gamma rather than editing the defaults. This governance discipline mirrors the fine-grained control patterns we cover for the lakehouse in AWS Lake Formation data governance.

Scaling with Celery and Redis

A single Gunicorn process is fine for a demo and hopeless for a busy instance. Superset offloads long-running work — asynchronous SQL Lab queries, alerts and reports, and dashboard thumbnails — to Celery workers backed by a message broker and a results backend. Redis is the common choice for both.

You define a CeleryConfig class in superset_config.py, assign it to CELERY_CONFIG, and set a RESULTS_BACKEND for query results. The web server and every worker must share this configuration.

# superset_config.py
from cachelib.redis import RedisCache

class CeleryConfig:
    broker_url = "redis://redis:6379/0"
    result_backend = "redis://redis:6379/0"
    broker_connection_retry_on_startup = True
    imports = (
        "superset.sql_lab",
        "superset.tasks.scheduler",
        "superset.tasks.thumbnails",
        "superset.tasks.cache",
    )

CELERY_CONFIG = CeleryConfig
RESULTS_BACKEND = RedisCache(host="redis", port=6379, db=0, key_prefix="superset_results")

Workers and the scheduler run as separate processes from the web server. In current Superset the command form puts the app before the subcommand.

# Start a worker (prefork pool, 4 processes)
celery --app=superset.tasks.celery_app:app worker --pool=prefork -O fair -c 4

# Start the beat scheduler for alerts, reports, and cache warmup
celery --app=superset.tasks.celery_app:app beat

Note

Run exactly one Celery beat process across your whole cluster. Two beats double-schedule everything — reports delivered twice, cache warmups stacking up, phantom load. Workers scale horizontally; beat does not. This is the single most common self-hosted Superset misconfiguration.

Caching Layers

Superset has several distinct caches, and mixing them up is a frequent source of confusion. DATA_CACHE_CONFIG caches the chart data queried from your datasets — the one that most directly affects dashboard speed. CACHE_CONFIG handles general metadata caching.

Two more are effectively required for a healthy instance: FILTER_STATE_CACHE_CONFIG for dashboard filter state and EXPLORE_FORM_DATA_CACHE_CONFIG for the Explore view’s form data. Each takes a CACHE_TYPERedisCache is typical — plus a timeout and key prefix.

# superset_config.py
DATA_CACHE_CONFIG = {
    "CACHE_TYPE": "RedisCache",
    "CACHE_DEFAULT_TIMEOUT": 3600,
    "CACHE_KEY_PREFIX": "superset_data",
    "CACHE_REDIS_URL": "redis://redis:6379/1",
}

For dashboards that must feel instant, pair caching with scheduled cache warmup via the beat scheduler, so the first user of the day hits a warm cache rather than a cold query. Redis does a lot of heavy lifting here — the same operational patterns from running Redis in production apply directly to a Superset deployment.

Production Checklist

  • Set a strong SECRET_KEY. Generate it, inject it via environment, and never ship the default — it signs sessions and encrypts stored credentials.
  • Use a shared metadata database. Postgres or MySQL, reachable by every web and worker node. Never SQLite in a cluster.
  • Run one beat, many workers. Scale workers horizontally; keep a single Celery beat to avoid duplicate scheduled jobs.
  • Layer roles, do not edit defaults. Build on Gamma with new roles; superset init will revert changes to built-in roles.
  • Do not rely on RLS alone. Restrict SQL Lab and database access so no role can query around Row-Level Security.
  • Deploy on Kubernetes for production. Use the official Helm chart; keep Docker Compose for local and evaluation only.

Superset’s appeal is that it is a genuinely capable BI platform you host yourself, with a semantic layer thin enough to stay portable and an access model rich enough for real multi-tenancy. The cost is operational: databases, brokers, caches, and roles are yours to run. Get those right and it scales a long way.

Superset is the presentation layer of a larger stack. It shines on top of a fast analytical engine like ClickHouse, draws its metric definitions from a transformation layer such as dbt, and if you prefer analytics-as-code instead of a UI, our guide to Evidence.dev data apps covers the other end of that spectrum.

Your BI vendor got acquired and repriced again so you want dashboards you actually own, a single Superset instance needs to serve many tenants without one customer ever seeing another's rows, or your self-hosted Superset falls over under load because it is running a lone Gunicorn process with no Celery workers, no shared metadata database, and duplicate reports from two beat schedulers?

We design and deploy production Apache Superset platforms — from a Kubernetes install with the official Helm chart rather than throwaway Docker Compose, through a shared Postgres metadata database and a strong injected SECRET_KEY, database connections pushed down to fast analytical engines like ClickHouse or Trino, a thin dataset semantic layer with reusable metrics and calculated columns and virtual datasets kept flat so metrics never double-aggregate, multi-tenant isolation built on Row-Level Security with Regular and Base filters and group_key-combined rules plus locked-down SQL Lab so no role can query around it, an access model layered on Gamma instead of editing built-in roles that superset init would revert, and horizontal Celery workers with exactly one beat scheduler and Redis-backed DATA_CACHE_CONFIG with scheduled cache warmup so dashboards stay fast — with every role and filter verified against your tenancy model before you trust it. Let's talk.

Let's Talk

DataSOps Consulting

Need help implementing this in production?

We build and operate data pipelines, AI systems, and observability stacks for engineering teams. Reach out for a free 30-minute architecture review.