Deployment & Infrastructure

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...

šŸ“…
šŸ“–8 min read

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
  • There's no place to preview how a dashboard redesign looks before it goes live
  • Database schema changes can't be tested against Metabase before they affect real users
  • Stakeholders can't approve a new report before it's published to customers
  • 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:

  • Create the admin account
  • Skip the "add your data" step (you'll add it via the API or serialization import)
  • 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:

    GroupDevStagingProduction
    DevelopersAdminAdminRead-only or no access
    QA / StakeholdersNo accessAdminRead-only
    Customers (embedded)No accessTest accountsReal accounts
    In production, developers typically don't have admin access to Metabase directly — changes must go through the CI pipeline. This prevents accidental production edits and ensures all changes are tracked in git.

    Configure developer access in production carefully:

  • Create a Developers (Read-Only) group with view access to all collections but no edit permissions
  • Never give developers the Metabase Administrator role in production
  • ---

    Preventing Production Edits

    The most common source of environment drift is developers making "quick fixes" directly in production. Prevent this by:

  • Restricting admin access — only your CI service account has admin access in production
  • Audit logging (Pro/Enterprise) — alerts on any content changes in production
  • Culture and process — make the promotion workflow fast enough that going through it is easier than the workaround
  • If a genuine emergency requires a production edit, have a documented process:

  • Make the change in production (document what was changed and why)
  • Immediately replicate the change in dev
  • Export, commit, and promote through the normal pipeline within 24 hours
  • ---

    Environment Parity: What to Keep Consistent

    For staging to be a reliable predictor of production behavior, keep these consistent:

    FactorShould Match?Notes
    Metabase versionYesIdentical image tags
    Database engineYesSame PostgreSQL version
    Instance sizeYesSame CPU/memory
    Connection namesYesRequired for serialization to work
    SSL configurationYesCatch cert issues early
    Data volumeApproximateStaging should be representative, not tiny
    User countApproximateSynthetic users for load testing
    What doesn't need to match:

  • Actual data values (staging uses sanitized data)
  • Customer accounts (staging uses test accounts)
  • Production database credentials
  • ---

    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:

  • Deploy the schema change to the dev database
  • Metabase's hourly schema sync picks it up in dev automatically (or trigger manually: Admin → Databases → Sync)
  • Update any affected questions in dev
  • Export and promote configuration through staging to production
  • Deploy the schema change to the staging and production databases
  • Trigger a manual schema sync in staging and production: POST /api/database/:id/sync_schema
  • The order matters — deploy the schema change to the database before Metabase configuration referencing new tables arrives.

    ---

    Backup and Recovery Per Environment

    EnvironmentBackup frequencyRTONotes
    DevNot requiredHoursCan be recreated from git
    StagingDaily2 hoursUseful for post-incident analysis
    ProductionDaily + point-in-time30 minutesFull backup strategy required
    The production Metabase application database (dashboards, questions, users) should be backed up daily with point-in-time recovery capability. In a disaster recovery scenario, you can rebuild Metabase from the application database backup plus the serialized config in git.

    ---

    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.