Metabase Environment Setup - Dev, Staging, and Production
A proper Metabase environment strategy runs three separate instances ā development, staging, and production ā each connected to an appropriate databas...
Metabase Environment Setup: Dev, Staging, and Production
A proper Metabase environment strategy runs three separate instances ā development, staging, and production ā each connected to an appropriate database tier, with configuration promoted through environments using Metabase's serialization feature or API-based scripts rather than being recreated manually. Separate environments prevent dashboard changes from breaking production, give stakeholders a stable preview environment, and enable the kind of change management that customer-facing analytics requires.
---
Why Separate Environments Matter
Without environment separation, every Metabase change is made directly in production:
- A developer editing a SQL query introduces a bug that breaks a customer-facing dashboard immediately
With environment separation, changes move through a pipeline with clear gates ā just like application code.
---
Environment Definitions
Development
Purpose: Individual developer experimentation, dashboard authoring, query development
Data source: Development database or a sanitized copy of production data (never real production data)
Who has access: Engineering team only
Change policy: Freely modified; no approval required
Infrastructure: Minimal ā a single Docker container with H2 or a small PostgreSQL instance is sufficient. Often runs locally on a developer's machine or on a shared dev server.
Key config:
bash
MB_SITE_URL=http://localhost:3000 MB_DB_TYPE=h2 # acceptable for dev
Staging
Purpose: QA, stakeholder preview, pre-production validation
Data source: Staging database with production-representative data (same schema, anonymized or synthetic data for PII fields)
Who has access: Engineering, QA, product managers, key stakeholders
Change policy: Applied automatically via CI when PRs are merged; no manual UI changes
Infrastructure: Matches production configuration as closely as possible (same instance size, same database tier), to catch configuration-dependent bugs
Key config:
bash
MB_SITE_URL=https://analytics-staging.yourapp.com MB_DB_TYPE=postgres MB_DB_HOST=staging-db.internal
Production
Purpose: Live customer-facing and internal analytics
Data source: Production database (read-only user)
Who has access: End users, customers (via embedding), admins
Change policy: Applied via CI only after staging validation and explicit approval; never modified through the UI directly
Infrastructure: Full production setup (ECS/Fargate, Kubernetes, or Metabase Cloud) with monitoring, alerting, and automated backups
Key config:
bash
MB_SITE_URL=https://analytics.yourapp.com MB_DB_TYPE=postgres MB_DB_HOST=prod-db.internal
---
Environment-Specific Configuration
Each environment has its own set of environment variables. Manage them with a tool that supports per-environment configuration: AWS Secrets Manager with separate namespaces, Kubernetes Secrets per namespace, or .env files per environment (never committed to git).
environments/
āāā dev/ ā āāā .env # not committed ā generated from template ā āāā .env.template # committed ā documents required variables āāā staging/ ā āāā .env.template āāā production/ āāā .env.template
.env.template (documents required variables without values):
bash
<h1 class="text-4xl font-bold mb-6 text-slate-900">Metabase application database</h1> MB_DB_TYPE=postgres MB_DB_HOST= MB_DB_PORT=5432 MB_DB_DBNAME=metabase MB_DB_USER= MB_DB_PASS= # managed by secrets manager
<h1 class="text-4xl font-bold mb-6 text-slate-900">Metabase configuration</h1> MB_SITE_URL= MB_SITE_NAME=Analytics MB_JETTY_PORT=3000
<h1 class="text-4xl font-bold mb-6 text-slate-900">Security</h1> MB_EMBEDDING_SECRET_KEY= # managed by secrets manager
<h1 class="text-4xl font-bold mb-6 text-slate-900">Performance</h1> JAVA_OPTS=-Xmx2g -Xms512m
<h1 class="text-4xl font-bold mb-6 text-slate-900">Telemetry (disable in all environments)</h1> MB_ANON_TRACKING_ENABLED=false
---
Sharing Configuration Across Environments
The Problem: Database IDs Differ Between Environments
Metabase assigns numeric IDs to databases, tables, and fields when they're first connected. These IDs are environment-specific ā the orders table might be field ID 42 in staging and field ID 87 in production. This makes it impossible to directly copy raw question definitions between environments.
The solution: Use Metabase's Serialization feature (Pro/Enterprise), which uses stable entity_id UUIDs instead of numeric IDs in exported YAML files. These UUIDs are consistent across environments when content is created through the serialization import process.
For open-source deployments without serialization, database connections must be set up with the same names across environments. Metabase will try to match connections by name when importing.
Syncing Configuration with Serialization
The serialization workflow:
1. Developer makes changes in DEV Metabase
ā ā¼
Export: metabase export ā ./metabase-config/
ā ā¼
git commit + PR
ā ā¼
CI: metabase import on STAGING
ā ā¼
QA validation in STAGING
ā ā¼
Approval + merge to main
ā ā¼
CI: metabase import on PRODUCTION
---
Setting Up a New Environment
When creating a new Metabase environment from scratch, the setup sequence matters:
Step 1: Start Metabase and Configure the Application Database
bash
docker run -d \ -e MB_DB_TYPE=postgres \ -e MB_DB_HOST=your-db-host \ -e MB_DB_DBNAME=metabase_staging \ -e MB_DB_USER=metabase_app \ -e MB_DB_PASS=your-password \ -e MB_SITE_URL=https://analytics-staging.yourapp.com \ -p 3000:3000 \ metabase/metabase:v0.50.0
Step 2: Complete Initial Setup
Open the instance in a browser and complete the setup wizard:
Step 3: Connect Databases
Connect your staging data sources via the UI or API. Use the same connection display names across environments ā serialization matches connections by name.
bash
curl -X POST \ -H "x-api-key: $STAGING_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Production DB", "engine": "postgres", "details": { ... } }' \ https://analytics-staging.yourapp.com/api/database
Step 4: Import Configuration from Dev
bash
<h1 class="text-4xl font-bold mb-6 text-slate-900">Export from dev</h1> docker exec metabase-dev java -jar /app/metabase.jar export /tmp/dev-export docker cp metabase-dev:/tmp/dev-export ./metabase-config
<h1 class="text-4xl font-bold mb-6 text-slate-900">Import to staging</h1> docker cp ./metabase-config metabase-staging:/tmp/staging-import docker exec metabase-staging java -jar /app/metabase.jar import /tmp/staging-import
---
Environment-Specific Permissions
Permission structures often differ between environments:
| Group | Dev | Staging | Production |
|---|---|---|---|
| Developers | Admin | Admin | Read-only or no access |
| QA / Stakeholders | No access | Admin | Read-only |
| Customers (embedded) | No access | Test accounts | Real accounts |
Configure developer access in production carefully:
Developers (Read-Only) group with view access to all collections but no edit permissions---
Preventing Production Edits
The most common source of environment drift is developers making "quick fixes" directly in production. Prevent this by:
If a genuine emergency requires a production edit, have a documented process:
---
Environment Parity: What to Keep Consistent
For staging to be a reliable predictor of production behavior, keep these consistent:
| Factor | Should Match? | Notes |
|---|---|---|
| Metabase version | Yes | Identical image tags |
| Database engine | Yes | Same PostgreSQL version |
| Instance size | Yes | Same CPU/memory |
| Connection names | Yes | Required for serialization to work |
| SSL configuration | Yes | Catch cert issues early |
| Data volume | Approximate | Staging should be representative, not tiny |
| User count | Approximate | Synthetic users for load testing |
---
Handling Database Schema Changes
When your application's database schema changes (new table, new column), Metabase needs to pick up the change in all environments:
POST /api/database/:id/sync_schemaThe order matters ā deploy the schema change to the database before Metabase configuration referencing new tables arrives.
---
Backup and Recovery Per Environment
| Environment | Backup frequency | RTO | Notes |
|---|---|---|---|
| Dev | Not required | Hours | Can be recreated from git |
| Staging | Daily | 2 hours | Useful for post-incident analysis |
| Production | Daily + point-in-time | 30 minutes | Full backup strategy required |
---
Summary
A three-environment Metabase strategy ā dev (free-form authoring), staging (CI-managed, production-representative), production (CI-managed, no direct edits) ā gives analytics changes the same change management rigor as application code. Configuration flows from dev to staging to production through serialization or API-based scripts, with the git repository as the source of truth. The key constraints are: keep connection display names consistent across environments (required for serialization), prevent direct production edits, and maintain environment parity in instance size and Metabase version.