This is an automated email from the ASF dual-hosted git repository.
rusackas pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/superset.git
The following commit(s) were added to refs/heads/master by this push:
new 91539f77aa feat(docker): support running multiple Superset instances
simultaneously (#36751)
91539f77aa is described below
commit 91539f77aa5d10c0c61b6a28fce2db8dd808e64c
Author: Evan Rusackas <[email protected]>
AuthorDate: Thu Dec 18 17:04:58 2025 -0800
feat(docker): support running multiple Superset instances simultaneously
(#36751)
Co-authored-by: Claude Opus 4.5 <[email protected]>
---
.envrc.example | 41 +++++
Makefile | 21 ++-
docker-compose.yml | 26 ++--
docker/.env | 9 ++
docker/.env-local.example | 39 +++++
docker/README.md | 28 ++++
.../contributing/development-setup.md | 33 ++++
scripts/docker-compose-up.sh | 171 +++++++++++++++++++++
8 files changed, 350 insertions(+), 18 deletions(-)
diff --git a/.envrc.example b/.envrc.example
new file mode 100644
index 0000000000..1d1f80ff3f
--- /dev/null
+++ b/.envrc.example
@@ -0,0 +1,41 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# Auto-configure Docker Compose for multi-instance support
+# Requires direnv: https://direnv.net/
+#
+# Install: brew install direnv (or apt install direnv)
+# Setup: Add 'eval "$(direnv hook bash)"' to ~/.bashrc (or ~/.zshrc)
+# Allow: Run 'direnv allow' in this directory once
+
+# Generate unique project name from directory
+export COMPOSE_PROJECT_NAME=$(basename "$PWD" | tr '[:upper:]' '[:lower:]' |
sed 's/[^a-z0-9]/-/g')
+
+# Find available ports sequentially to avoid collisions
+_is_free() { ! lsof -i ":$1" &>/dev/null 2>&1; }
+
+_p=80; while ! _is_free $_p; do ((_p++)); done; export NGINX_PORT=$_p
+_p=8088; while ! _is_free $_p; do ((_p++)); done; export SUPERSET_PORT=$_p
+_p=9000; while ! _is_free $_p; do ((_p++)); done; export NODE_PORT=$_p
+_p=8080; while ! _is_free $_p || [ $_p -eq $NGINX_PORT ]; do ((_p++)); done;
export WEBSOCKET_PORT=$_p
+_p=8081; while ! _is_free $_p || [ $_p -eq $WEBSOCKET_PORT ]; do ((_p++));
done; export CYPRESS_PORT=$_p
+_p=5432; while ! _is_free $_p; do ((_p++)); done; export DATABASE_PORT=$_p
+_p=6379; while ! _is_free $_p; do ((_p++)); done; export REDIS_PORT=$_p
+
+unset _p _is_free
+
+echo "🐳 Superset configured: http://localhost:$SUPERSET_PORT (dev:
localhost:$NODE_PORT)"
diff --git a/Makefile b/Makefile
index 4a7121fd34..9d0a73c9e7 100644
--- a/Makefile
+++ b/Makefile
@@ -18,7 +18,7 @@
# Python version installed; we need 3.10-3.11
PYTHON=`command -v python3.11 || command -v python3.10`
-.PHONY: install superset venv pre-commit
+.PHONY: install superset venv pre-commit up down logs ps nuke
install: superset pre-commit
@@ -112,3 +112,22 @@ report-celery-beat:
admin-user:
superset fab create-admin
+
+# Docker Compose with auto-assigned ports (for running multiple instances)
+up:
+ ./scripts/docker-compose-up.sh
+
+up-detached:
+ ./scripts/docker-compose-up.sh -d
+
+down:
+ ./scripts/docker-compose-up.sh down
+
+logs:
+ ./scripts/docker-compose-up.sh logs -f
+
+ps:
+ ./scripts/docker-compose-up.sh ps
+
+nuke:
+ ./scripts/docker-compose-up.sh nuke
diff --git a/docker-compose.yml b/docker-compose.yml
index 050c6b22ef..5930951776 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -54,10 +54,9 @@ services:
- path: docker/.env-local # optional override
required: false
image: nginx:latest
- container_name: superset_nginx
restart: unless-stopped
ports:
- - "80:80"
+ - "${NGINX_PORT:-80}:80"
extra_hosts:
- "host.docker.internal:host-gateway"
volumes:
@@ -66,10 +65,9 @@ services:
redis:
image: redis:7
- container_name: superset_cache
restart: unless-stopped
ports:
- - "127.0.0.1:6379:6379"
+ - "127.0.0.1:${REDIS_PORT:-6379}:6379"
volumes:
- redis:/data
@@ -80,10 +78,9 @@ services:
- path: docker/.env-local # optional override
required: false
image: postgres:16
- container_name: superset_db
restart: unless-stopped
ports:
- - "127.0.0.1:5432:5432"
+ - "127.0.0.1:${DATABASE_PORT:-5432}:5432"
volumes:
- db_home:/var/lib/postgresql/data
- ./docker/docker-entrypoint-initdb.d:/docker-entrypoint-initdb.d
@@ -96,13 +93,12 @@ services:
required: false
build:
<<: *common-build
- container_name: superset_app
command: ["/app/docker/docker-bootstrap.sh", "app"]
restart: unless-stopped
ports:
- - 8088:8088
+ - ${SUPERSET_PORT:-8088}:8088
# When in cypress-mode ->
- - 8081:8081
+ - ${CYPRESS_PORT:-8081}:8081
extra_hosts:
- "host.docker.internal:host-gateway"
user: *superset-user
@@ -114,10 +110,9 @@ services:
SUPERSET__SQLALCHEMY_EXAMPLES_URI: "duckdb:////app/data/examples.duckdb"
superset-websocket:
- container_name: superset_websocket
build: ./superset-websocket
ports:
- - 8080:8080
+ - ${WEBSOCKET_PORT:-8080}:8080
extra_hosts:
- "host.docker.internal:host-gateway"
depends_on:
@@ -149,7 +144,6 @@ services:
superset-init:
build:
<<: *common-build
- container_name: superset_init
command: ["/app/docker/docker-init.sh"]
env_file:
- path: docker/.env # default
@@ -186,9 +180,10 @@ services:
SCARF_ANALYTICS: "${SCARF_ANALYTICS:-}"
# configuring the dev-server to use the host.docker.internal to connect
to the backend
superset: "http://superset:8088"
+ # Bind to all interfaces so Docker port mapping works
+ WEBPACK_DEVSERVER_HOST: "0.0.0.0"
ports:
- - "127.0.0.1:9000:9000" # exposing the dynamic webpack dev server
- container_name: superset_node
+ - "127.0.0.1:${NODE_PORT:-9000}:9000" # exposing the dynamic webpack
dev server
command: ["/app/docker/docker-frontend.sh"]
env_file:
- path: docker/.env # default
@@ -200,7 +195,6 @@ services:
superset-worker:
build:
<<: *common-build
- container_name: superset_worker
command: ["/app/docker/docker-bootstrap.sh", "worker"]
env_file:
- path: docker/.env # default
@@ -226,7 +220,6 @@ services:
superset-worker-beat:
build:
<<: *common-build
- container_name: superset_worker_beat
command: ["/app/docker/docker-bootstrap.sh", "beat"]
env_file:
- path: docker/.env # default
@@ -244,7 +237,6 @@ services:
superset-tests-worker:
build:
<<: *common-build
- container_name: superset_tests_worker
command: ["/app/docker/docker-bootstrap.sh", "worker"]
env_file:
- path: docker/.env # default
diff --git a/docker/.env b/docker/.env
index a0cbb47deb..def1be0d25 100644
--- a/docker/.env
+++ b/docker/.env
@@ -21,6 +21,15 @@ PYTHONUNBUFFERED=1
COMPOSE_PROJECT_NAME=superset
DEV_MODE=true
+# Port configuration (override in .env-local for multiple instances)
+# NGINX_PORT=80
+# SUPERSET_PORT=8088
+# NODE_PORT=9000
+# WEBSOCKET_PORT=8080
+# CYPRESS_PORT=8081
+# DATABASE_PORT=5432
+# REDIS_PORT=6379
+
# database configurations (do not modify)
DATABASE_DB=superset
DATABASE_HOST=db
diff --git a/docker/.env-local.example b/docker/.env-local.example
new file mode 100644
index 0000000000..433c316091
--- /dev/null
+++ b/docker/.env-local.example
@@ -0,0 +1,39 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# -----------------------------------------------------------------------
+# Example .env-local file for running multiple Superset instances
+# Copy this file to .env-local and customize for your setup
+# -----------------------------------------------------------------------
+
+# Unique project name prevents container/volume conflicts between clones
+# Each clone should have a different name (e.g., superset-pr123,
superset-feature-x)
+COMPOSE_PROJECT_NAME=superset-dev2
+
+# Port offsets for running multiple instances simultaneously
+# Instance 1 (default): 80, 8088, 9000, 8080, 8081, 5432, 6379
+# Instance 2 example: 81, 8089, 9001, 8082, 8083, 5433, 6380
+NGINX_PORT=81
+SUPERSET_PORT=8089
+NODE_PORT=9001
+WEBSOCKET_PORT=8082
+CYPRESS_PORT=8083
+DATABASE_PORT=5433
+REDIS_PORT=6380
+
+# For verbose logging during development:
+# SUPERSET_LOG_LEVEL=debug
diff --git a/docker/README.md b/docker/README.md
index eeb2a8f359..1ef46aa13a 100644
--- a/docker/README.md
+++ b/docker/README.md
@@ -77,6 +77,34 @@ To run the container, simply run: `docker compose up`
After waiting several minutes for Superset initialization to finish, you can
open a browser and view [`http://localhost:8088`](http://localhost:8088)
to start your journey.
+### Running Multiple Instances
+
+If you need to run multiple Superset instances simultaneously (e.g., different
branches or clones), use the make targets which automatically find available
ports:
+
+```bash
+make up
+```
+
+This automatically:
+- Generates a unique project name from your directory
+- Finds available ports (incrementing from defaults if in use)
+- Displays the assigned URLs before starting
+
+Available commands (run from repo root):
+
+| Command | Description |
+|---------|-------------|
+| `make up` | Start services (foreground) |
+| `make up-detached` | Start services (background) |
+| `make down` | Stop all services |
+| `make ps` | Show running containers |
+| `make logs` | Follow container logs |
+| `make nuke` | Stop, remove volumes & local images |
+
+From a subdirectory, use: `make -C $(git rev-parse --show-toplevel) up`
+
+**Important**: Always use these commands instead of plain `docker compose
down`, which won't know the correct project name.
+
## Developing
While running, the container server will reload on modification of the
Superset Python and JavaScript source code.
diff --git a/docs/developer_portal/contributing/development-setup.md
b/docs/developer_portal/contributing/development-setup.md
index a25cca8dbc..464c70a73c 100644
--- a/docs/developer_portal/contributing/development-setup.md
+++ b/docs/developer_portal/contributing/development-setup.md
@@ -139,6 +139,39 @@ docker volume rm superset_db_home
docker-compose up
```
+### Running multiple instances
+
+If you need to run multiple Superset clones simultaneously (e.g., testing
different branches),
+use `make up` instead of `docker compose up`:
+
+```bash
+make up
+```
+
+This automatically:
+- Generates a unique project name from your directory name
+- Finds available ports (incrementing from 8088, 9000, etc. if already in use)
+- Displays the assigned URLs before starting
+
+Each clone gets isolated containers and volumes, so you can run them
side-by-side without conflicts.
+
+Available commands (run from repo root):
+
+| Command | Description |
+|---------|-------------|
+| `make up` | Start services (foreground) |
+| `make up-detached` | Start services (background) |
+| `make down` | Stop all services |
+| `make ps` | Show running containers |
+| `make logs` | Follow container logs |
+| `make nuke` | Stop, remove volumes & local images |
+
+From a subdirectory, use: `make -C $(git rev-parse --show-toplevel) up`
+
+:::warning
+Always use these commands instead of plain `docker compose down`, which won't
know the correct project name for your instance.
+:::
+
## GitHub Codespaces (Cloud Development)
GitHub Codespaces provides a complete, pre-configured development environment
in the cloud. This is ideal for:
diff --git a/scripts/docker-compose-up.sh b/scripts/docker-compose-up.sh
new file mode 100755
index 0000000000..525d07e3c2
--- /dev/null
+++ b/scripts/docker-compose-up.sh
@@ -0,0 +1,171 @@
+#!/usr/bin/env bash
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# -----------------------------------------------------------------------
+# Smart docker-compose wrapper for running multiple Superset instances
+#
+# Features:
+# - Auto-generates unique project name from directory
+# - Finds available ports automatically
+# - No manual .env-local editing needed
+#
+# Usage:
+# ./scripts/docker-compose-up.sh [docker-compose args...]
+#
+# Examples:
+# ./scripts/docker-compose-up.sh # Start all services
+# ./scripts/docker-compose-up.sh -d # Start detached
+# ./scripts/docker-compose-up.sh down # Stop services
+# -----------------------------------------------------------------------
+
+set -e
+
+# Get the repo root directory
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
+
+# Generate project name from directory name (sanitized for Docker)
+DIR_NAME=$(basename "$REPO_ROOT")
+PROJECT_NAME=$(echo "$DIR_NAME" | tr '[:upper:]' '[:lower:]' | sed
's/[^a-z0-9]/-/g' | sed 's/--*/-/g' | sed 's/^-//' | sed 's/-$//')
+
+# Function to check if a port is available
+is_port_available() {
+ local port=$1
+ if command -v lsof &> /dev/null; then
+ ! lsof -i ":$port" &> /dev/null
+ elif command -v netstat &> /dev/null; then
+ ! netstat -tuln 2>/dev/null | grep -q ":$port "
+ elif command -v ss &> /dev/null; then
+ ! ss -tuln 2>/dev/null | grep -q ":$port "
+ else
+ # If no tool available, assume port is available
+ return 0
+ fi
+}
+
+# Track ports we've already claimed in this session
+CLAIMED_PORTS=""
+
+# Function to check if port is available and not already claimed
+is_port_free() {
+ local port=$1
+ # Check not already claimed by us
+ if [[ " $CLAIMED_PORTS " =~ " $port " ]]; then
+ return 1
+ fi
+ # Check not in use on system
+ is_port_available $port
+}
+
+# Function to find and claim next available port starting from base
+# Sets the result in the variable named by $2
+find_and_claim_port() {
+ local base_port=$1
+ local var_name=$2
+ local max_attempts=100
+ local port=$base_port
+
+ for ((i=0; i<max_attempts; i++)); do
+ if is_port_free $port; then
+ CLAIMED_PORTS="$CLAIMED_PORTS $port"
+ eval "$var_name=$port"
+ return 0
+ fi
+ ((port++))
+ done
+
+ echo "ERROR: Could not find available port starting from $base_port" >&2
+ return 1
+}
+
+# Base ports (defaults from docker-compose.yml)
+BASE_NGINX=80
+BASE_SUPERSET=8088
+BASE_NODE=9000
+BASE_WEBSOCKET=8080
+BASE_CYPRESS=8081
+BASE_DATABASE=5432
+BASE_REDIS=6379
+
+# Find available ports (no subshells - claims persist correctly)
+echo "🔍 Finding available ports..."
+find_and_claim_port $BASE_NGINX NGINX_PORT
+find_and_claim_port $BASE_SUPERSET SUPERSET_PORT
+find_and_claim_port $BASE_NODE NODE_PORT
+find_and_claim_port $BASE_WEBSOCKET WEBSOCKET_PORT
+find_and_claim_port $BASE_CYPRESS CYPRESS_PORT
+find_and_claim_port $BASE_DATABASE DATABASE_PORT
+find_and_claim_port $BASE_REDIS REDIS_PORT
+
+# Export for docker-compose
+export COMPOSE_PROJECT_NAME="$PROJECT_NAME"
+export NGINX_PORT
+export SUPERSET_PORT
+export NODE_PORT
+export WEBSOCKET_PORT
+export CYPRESS_PORT
+export DATABASE_PORT
+export REDIS_PORT
+
+echo ""
+echo "🐳 Starting Superset with:"
+echo " Project: $PROJECT_NAME"
+echo " Superset: http://localhost:$SUPERSET_PORT"
+echo " Dev Server: http://localhost:$NODE_PORT"
+echo " Nginx: http://localhost:$NGINX_PORT"
+echo " WebSocket: localhost:$WEBSOCKET_PORT"
+echo " Database: localhost:$DATABASE_PORT"
+echo " Redis: localhost:$REDIS_PORT"
+echo ""
+
+# Change to repo root
+cd "$REPO_ROOT"
+
+# Handle special commands
+case "${1:-}" in
+ --dry-run)
+ echo "✅ Dry run complete. To start, run without --dry-run"
+ exit 0
+ ;;
+ --env)
+ # Output as sourceable environment variables
+ echo "export COMPOSE_PROJECT_NAME='$PROJECT_NAME'"
+ echo "export NGINX_PORT=$NGINX_PORT"
+ echo "export SUPERSET_PORT=$SUPERSET_PORT"
+ echo "export NODE_PORT=$NODE_PORT"
+ echo "export WEBSOCKET_PORT=$WEBSOCKET_PORT"
+ echo "export CYPRESS_PORT=$CYPRESS_PORT"
+ echo "export DATABASE_PORT=$DATABASE_PORT"
+ echo "export REDIS_PORT=$REDIS_PORT"
+ exit 0
+ ;;
+ down|stop|logs|ps|exec|restart)
+ # Pass through to docker compose
+ docker compose "$@"
+ ;;
+ nuke)
+ # Nuclear option: remove everything (containers, volumes, local images)
+ echo "💥 Nuking all containers, volumes, and locally-built images for
$PROJECT_NAME..."
+ docker compose down -v --rmi local
+ echo "✅ Done. Run 'make up' or './scripts/docker-compose-up.sh' to
start fresh."
+ ;;
+ *)
+ # Default: start services
+ docker compose up "$@"
+ ;;
+esac