bluegreencity-ki

Apache Superset on the Workstation

Last updated: 2026-04-02

This guide documents how to install and expose Apache Superset on the shared workstation by using the upstream Apache Superset Docker Compose setup on Windows. It is intended for administrative setup and maintenance.

Important scope note:

Prerequisites

git clone https://github.com/apache/superset.git

1. Resolve the Port Conflict with PostGIS

The upstream docker-compose.yml binds the internal Superset database to 127.0.0.1:5432 by default. On the workstation this conflicts with an already running PostGIS container.

Create a root-level .env file in the Superset repository root, next to docker-compose.yml:

DATABASE_PORT=5433

Important:

In docker/.env, keep the internal container port on 5432:

DATABASE_PORT=5432

Verify the effective port mapping before starting the stack:

docker compose config | findstr /i "published"

Expected result:

2. Generate and Configure Local Secrets

Generate a secret key:

python -c "import secrets; print(secrets.token_hex(42))"

Create docker/.env-local if it does not exist yet and place the generated value there:

SUPERSET_SECRET_KEY='<your-generated-key>'

If the generated key contains special characters such as $, !, or #, wrap it in single quotes.

Recommended additional local overrides in docker/.env-local:

SUPERSET_LOAD_EXAMPLES=no
TAILSCALE_HOSTNAME=<your-hostname>.ts.net
MCP_PORT=5008
MCP_DEV_USERNAME=admin
MCP_AUTH_ENABLED=false

3. Start Superset

Run the initial build:

docker compose up --build

Notes:

docker compose up -d
docker compose up -d --force-recreate --no-deps superset nginx

4. Enable HTTPS via Tailscale

4.1 Enable Tailscale HTTPS Certificates

Open the Tailscale admin console:

Enable HTTPS Certificates for the tailnet.

4.2 Generate the Certificate

Run the following command in WSL or Git Bash on the workstation:

tailscale cert '<workstation-tailscale-domain>'

This creates two files:

Create the local certificate target folder in the Superset repository and copy both files into it:

mkdir docker\local\certs

Target folder:

4.3 Create a Local nginx Template

Create docker/local/nginx/superset.conf.template with the following configuration:

upstream superset_app {
    server host.docker.internal:8088;
    keepalive 100;
}
upstream superset_websocket {
    server host.docker.internal:8080;
    keepalive 100;
}

# Redirect HTTP to HTTPS
server {
    listen 80 default_server;
    server_name _;
    return 301 https://$host$request_uri;
}

# HTTPS server
server {
    listen 443 ssl default_server;
    server_name _;

    ssl_certificate     /etc/nginx/certs/${TAILSCALE_HOSTNAME}.crt;
    ssl_certificate_key /etc/nginx/certs/${TAILSCALE_HOSTNAME}.key;
    ssl_protocols       TLSv1.2 TLSv1.3;
    ssl_ciphers         HIGH:!aNULL:!MD5;

    location /ws {
        proxy_pass http://superset_websocket;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "Upgrade";
        proxy_set_header Host $host;
    }
    location ${SUPERSET_APP_ROOT}/static {
        proxy_pass http://host.docker.internal:9000;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
    }
    location ${SUPERSET_APP_ROOT} {
        proxy_pass http://superset_app;
        proxy_set_header Host $http_host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto https;
        proxy_http_version 1.1;
        port_in_redirect off;
        proxy_connect_timeout 300;
    }
}

4.4 Add the Tailscale Hostname to docker/.env

Add the Tailscale hostname to docker/.env-local:

TAILSCALE_HOSTNAME=<your-hostname>.ts.net

4.5 Update the nginx Service in docker-compose.yml

Create a local docker-compose.override.yml in the Superset repository root so that HTTPS and the certificate mount are enabled without modifying the tracked upstream docker-compose.yml:

nginx:
  ports:
    - "${NGINX_PORT:-80}:80"
    - "443:443"
  volumes:
    - ./docker/local/nginx/superset.conf.template:/etc/nginx/templates/superset.conf.template:ro
    - ./docker/local/certs:/etc/nginx/certs:ro

4.6 Restart nginx

Apply the nginx changes:

docker compose up -d --force-recreate --no-build nginx

5. Enable MCP Without Modifying Tracked Upstream Files

Create an ignored superset_config.py in the Superset repository root and mount it via docker-compose.override.yml.

Minimal local superset_config.py:

import importlib.util
import os
from pathlib import Path


def _load_base_config() -> None:
    base_config_path = Path("/app/docker/pythonpath_dev/superset_config.py")
    spec = importlib.util.spec_from_file_location(
        "superset_base_config",
        base_config_path,
    )
    if spec is None or spec.loader is None:
        raise RuntimeError(f"Unable to load base Superset config from {base_config_path}")

    module = importlib.util.module_from_spec(spec)
    spec.loader.exec_module(module)

    for key in dir(module):
        if key.isupper():
            globals()[key] = getattr(module, key)


_load_base_config()

FAB_ADD_SECURITY_API = True
MCP_SERVICE_HOST = "0.0.0.0"
MCP_SERVICE_PORT = int(os.getenv("MCP_PORT", "5008"))
MCP_AUTH_ENABLED = os.getenv("MCP_AUTH_ENABLED", "false").lower() == "true"
MCP_DEBUG = os.getenv("MCP_DEBUG", "false").lower() == "true"

if MCP_AUTH_ENABLED:
    jwt_algorithm = os.getenv("MCP_JWT_ALGORITHM")
    if jwt_algorithm:
        MCP_JWT_ALGORITHM = jwt_algorithm
else:
    MCP_DEV_USERNAME = os.getenv("MCP_DEV_USERNAME", "admin")

tailscale_hostname = os.getenv("TAILSCALE_HOSTNAME")
if tailscale_hostname:
    MCP_SERVICE_URL = f"https://{tailscale_hostname}"

Extend docker-compose.override.yml so the Superset containers and the MCP container use that config file, and add an mcp service:

superset:
  command: ["/bin/bash", "-lc", "export DEV_MODE=false && /app/.venv/bin/gunicorn --bind ${SUPERSET_BIND_ADDRESS:-0.0.0.0}:${SUPERSET_PORT:-8088} --access-logfile ${ACCESS_LOG_FILE:--} --error-logfile ${ERROR_LOG_FILE:--} --workers ${SERVER_WORKER_AMOUNT:-1} --worker-class ${SERVER_WORKER_CLASS:-gthread} --threads ${SERVER_THREADS_AMOUNT:-20} --log-level ${GUNICORN_LOGLEVEL:-info} --timeout ${GUNICORN_TIMEOUT:-60} --keep-alive ${GUNICORN_KEEPALIVE:-2} --max-requests ${WORKER_MAX_REQUESTS:-0} --max-requests-jitter ${WORKER_MAX_REQUESTS_JITTER:-0} --limit-request-line ${SERVER_LIMIT_REQUEST_LINE:-0} --limit-request-field_size ${SERVER_LIMIT_REQUEST_FIELD_SIZE:-0} 'superset.app:create_app()'"]
  environment:
    DEV_MODE: "false"
    SUPERSET_CONFIG_PATH: /app/superset_config.py
  volumes:
    - ./superset_config.py:/app/superset_config.py:ro

superset-init:
  environment:
    DEV_MODE: "false"
    SUPERSET_CONFIG_PATH: /app/superset_config.py
  volumes:
    - ./superset_config.py:/app/superset_config.py:ro

superset-worker:
  environment:
    DEV_MODE: "false"
    SUPERSET_CONFIG_PATH: /app/superset_config.py
  volumes:
    - ./superset_config.py:/app/superset_config.py:ro

superset-worker-beat:
  environment:
    DEV_MODE: "false"
    SUPERSET_CONFIG_PATH: /app/superset_config.py
  volumes:
    - ./superset_config.py:/app/superset_config.py:ro

mcp:
  image: superset-superset:latest
  command: ["/bin/bash", "-lc", "export DEV_MODE=false && /app/.venv/bin/superset mcp run --host 0.0.0.0 --port ${MCP_PORT:-5008} --debug"]
  env_file:
    - path: docker/.env
      required: true
    - path: docker/.env-local
      required: false
  environment:
    SUPERSET_CONFIG_PATH: /app/superset_config.py
  restart: unless-stopped
  ports:
    - "127.0.0.1:${MCP_PORT:-5008}:5008"
  healthcheck:
    test:
      - CMD-SHELL
      - >-
        curl -fsS -X POST http://127.0.0.1:5008/mcp
        -H 'Accept: application/json, text/event-stream'
        -H 'Content-Type: application/json'
        --data '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"docker-healthcheck","version":"1.0"}}}'
        | grep -q '"protocolVersion":"2024-11-05"'
    interval: 30s
    timeout: 10s
    retries: 3
    start_period: 20s
  volumes:
    - ./superset_config.py:/app/superset_config.py:ro

Important:

Start MCP locally:

docker compose up -d --force-recreate --no-build mcp

Local MCP endpoint for VS Code:

http://127.0.0.1:5008/mcp

6. Verify Access

Check the service locally:

curl http://localhost:8088/health

Check the service over Tailscale HTTPS:

curl https://<hostname>.ts.net/health

Expected response:

OK

Open Superset in a browser:

https://<hostname>.ts.net

Default login after initial setup:

Change the default password immediately.

7. Connect the Existing PostGIS as a Data Source

Superset can connect to the existing PostGIS container when both services share a Docker network.

Inspect the network of the running PostGIS container:

docker inspect <postgis-container-name> | findstr /i "network"

If Superset and PostGIS are on different Docker networks, add the external PostGIS network to the Superset stack. Under the relevant superset and db services, add:

networks:
  - default
  - postgis_network

networks:
  postgis_network:
    external: true
    name: <exact-network-name>

Use docker network ls to find the exact external network name.

Then create the database connection in Superset under Settings -> Database Connections -> + Database using:

postgresql+psycopg2://user:password@<postgis-container-name>:5432/your_db

File Reference Summary

File Purpose
.env in Superset repo root Sets DATABASE_PORT=5433 for Docker Compose port publishing
docker/.env Upstream default container runtime values
docker/.env-local Local ignored overrides such as SUPERSET_SECRET_KEY, TAILSCALE_HOSTNAME, and MCP settings
docker/local/nginx/superset.conf.template Local ignored nginx HTTPS template
docker/local/certs/ Local ignored Tailscale TLS certificate and key files
docker-compose.override.yml Local ignored Docker Compose overrides for HTTPS and MCP
superset_config.py Local ignored Superset runtime config including MCP and security API settings

Quick Command Reference

# First start with image build
docker compose up --build

# Normal start
docker compose up -d

# Recreate the web layer after local override changes
docker compose up -d --force-recreate --no-deps superset nginx

# Recreate MCP after config or healthcheck changes
docker compose up -d --force-recreate --no-deps mcp

# Stop all services
docker compose down

# Restart only nginx after config changes
docker compose up -d --no-build nginx

# Show running containers and published ports
docker ps --format "table \t"

# Follow logs
docker compose logs -f superset
docker compose logs -f superset-init
docker compose logs -f mcp