Deployment & Infrastructure

Backing Up and Restoring Your Metabase Instance

Backing up Metabase means backing up its application database — the PostgreSQL (or MySQL) instance that stores all of Metabase's configuration: dashbo...

šŸ“…
šŸ“–8 min read

Backing Up and Restoring Your Metabase Instance

Backing up Metabase means backing up its application database — the PostgreSQL (or MySQL) instance that stores all of Metabase's configuration: dashboards, questions, collections, users, permissions, database connections, and settings. Your analytics data lives in your own databases and is not stored in Metabase. A complete Metabase backup strategy combines automated database backups with serialized content exports committed to version control, giving you both point-in-time recovery and human-readable configuration history.

---

What Needs to Be Backed Up

The Application Database (Critical)

The Metabase application database contains everything that defines your Metabase instance:

What's storedExamples
Dashboard definitionsLayout, cards, filters, parameters
Question/card definitionsQueries, visualization settings
CollectionsFolder structure, permissions
User accountsEmail, name, group memberships, login attributes
Permission groupsGroup definitions and memberships
Database connectionsHost, port, credentials (encrypted)
Instance settingsSite URL, auth config, embedding secret
Pulse/alert configsScheduled report settings
This is the only thing you need to back up. Your analytics data (the data in your connected databases) is not stored in Metabase — it stays in your own databases.

What You Do Not Need to Back Up

  • The Metabase Docker image or JAR file (pull from Docker Hub on restore)
  • Your connected analytics databases (those have their own backup strategy)
  • Metabase logs (useful for debugging but not required for recovery)
  • ---

    Backup Methods

    Method 1: Database-Level Backups (Primary)

    The most reliable backup is a direct PostgreSQL dump of the application database. This captures the complete state at a point in time and can be restored to any compatible PostgreSQL instance.

    Manual backup with pg_dump:

    bash
    

    <h1 class="text-4xl font-bold mb-6 text-slate-900">From your database host or a container with pg_dump installed</h1> pg_dump \ -h your-db-host \ -U metabase_app \ -d metabase \ --format=custom \ # compressed, supports selective restore --no-owner \ # don't include ownership commands --no-privileges \ # don't include grant statements -f metabase-backup-$(date +%Y%m%d-%H%M%S).dump

    From a Docker container:

    bash
    

    docker exec metabase-db \ pg_dump -U metabase_app metabase \ > metabase-backup-$(date +%Y%m%d).sql

    Automated backup script:

    bash
    

    #!/bin/bash <h1 class="text-4xl font-bold mb-6 text-slate-900">backup-metabase.sh — run via cron or as a Kubernetes CronJob</h1>

    set -euo pipefail

    BACKUP_DIR="/backups/metabase" RETENTION_DAYS=30 DATE=$(date +%Y%m%d-%H%M%S) BACKUP_FILE="$BACKUP_DIR/metabase-$DATE.dump"

    mkdir -p "$BACKUP_DIR"

    pg_dump \ -h "$DB_HOST" \ -U "$DB_USER" \ -d "$DB_NAME" \ --format=custom \ -f "$BACKUP_FILE"

    echo "Backup created: $BACKUP_FILE ($(du -h $BACKUP_FILE | cut -f1))"

    <h1 class="text-4xl font-bold mb-6 text-slate-900">Upload to S3</h1> aws s3 cp "$BACKUP_FILE" "s3://your-backup-bucket/metabase/$DATE.dump" echo "Uploaded to S3"

    <h1 class="text-4xl font-bold mb-6 text-slate-900">Clean up local files older than retention period</h1> find "$BACKUP_DIR" -name "*.dump" -mtime +$RETENTION_DAYS -delete echo "Cleaned up backups older than $RETENTION_DAYS days"

    Kubernetes CronJob for automated backups:

    yaml
    

    <h1 class="text-4xl font-bold mb-6 text-slate-900">backup-cronjob.yaml</h1> apiVersion: batch/v1 kind: CronJob metadata: name: metabase-backup namespace: metabase spec: schedule: "0 3 <em class="italic"> </em> *" # daily at 3am successfulJobsHistoryLimit: 7 failedJobsHistoryLimit: 3 jobTemplate: spec: template: spec: restartPolicy: OnFailure containers: - name: backup image: postgres:15-alpine command: - /bin/sh - -c - | pg_dump \ -h $DB_HOST \ -U $DB_USER \ -d $DB_NAME \ --format=custom \ -f /tmp/backup.dump && \ aws s3 cp /tmp/backup.dump \ s3://your-backup-bucket/metabase/$(date +%Y%m%d).dump env: - name: DB_HOST value: "your-db-host" - name: DB_USER value: "metabase_app" - name: DB_NAME value: "metabase" - name: PGPASSWORD valueFrom: secretKeyRef: name: metabase-secrets key: MB_DB_PASS

    Method 2: Managed Database Automated Backups

    For deployments using managed databases (AWS RDS, Google Cloud SQL, Azure Database for PostgreSQL), the database service handles backups automatically:

    AWS RDS:

    hcl
    

    resource "aws_db_instance" "metabase" { backup_retention_period = 7 # 7 days of automated backups backup_window = "03:00-04:00" copy_tags_to_snapshot = true delete_automated_backups = false

    # Enable point-in-time recovery # RDS automatically captures transaction logs for PITR within the retention window }

    RDS automated backups are stored in S3 and support point-in-time recovery to any second within the retention window. This is the most convenient approach for AWS deployments — no additional scripts required.

    Method 3: Serialization Export (Configuration as Code)

    Metabase's Serialization feature (Pro/Enterprise) exports dashboards, questions, and collections as YAML files. These should be committed to git as a secondary backup:

    bash
    

    <h1 class="text-4xl font-bold mb-6 text-slate-900">Export via CLI</h1> java -jar /app/metabase.jar export /tmp/metabase-export <h1 class="text-4xl font-bold mb-6 text-slate-900">or via API</h1> curl -X POST \ -H "x-api-key: $METABASE_API_KEY" \ "$METABASE_SITE_URL/api/ee/serialization/export" \ -o metabase-config.zip

    Serialization complements but does not replace database backups:

    What's includedDatabase backupSerialization export
    Dashboards and questionsāœ“āœ“
    Collectionsāœ“āœ“
    Users and passwordsāœ“āœ—
    Database connectionsāœ“ (credentials encrypted)āœ—
    Permission groupsāœ“āœ—
    Instance settingsāœ“Partial
    Git historyāœ—āœ“
    Human-readableNoYes
    Importable to different instanceWith effortYes (designed for this)
    The combination of daily database backups + serialization exports committed to git gives you both complete recovery capability and an auditable history of configuration changes.

    ---

    Backup Schedule Recommendations

    EnvironmentDatabase backupSerialization exportRetention
    ProductionDaily automated + PITR (RDS)On every change (git)30 days
    StagingDaily automatedOn every change (git)7 days
    DevNot requiredOn every change (git)N/A
    For production deployments with embedded customer analytics, daily automated backups with PITR capability are the minimum. Anything less risks losing customer-facing dashboard configurations that may have taken significant effort to build.

    ---

    Restoring From Backup

    Full Restore (Database Backup)

    Use this when recovering from a complete failure — corrupted database, accidental deletion, or infrastructure failure.

    Step 1: Start a new Metabase instance pointed at a fresh database

    bash
    

    docker run -d \ -e MB_DB_TYPE=postgres \ -e MB_DB_HOST=your-new-db-host \ -e MB_DB_DBNAME=metabase_restored \ -e MB_DB_USER=metabase_app \ -e MB_DB_PASS=your-password \ -p 3001:3000 \ # different port to avoid conflicts metabase/metabase:v0.50.0

    Step 2: Restore the database backup

    bash
    

    <h1 class="text-4xl font-bold mb-6 text-slate-900">Create the target database</h1> psql -h your-new-db-host -U postgres -c "CREATE DATABASE metabase_restored;"

    <h1 class="text-4xl font-bold mb-6 text-slate-900">Restore from custom format dump</h1> pg_restore \ -h your-new-db-host \ -U metabase_app \ -d metabase_restored \ --no-owner \ --no-privileges \ metabase-backup-20240115.dump

    <h1 class="text-4xl font-bold mb-6 text-slate-900">Or restore from SQL dump</h1> psql -h your-new-db-host -U metabase_app -d metabase_restored \ < metabase-backup-20240115.sql

    Step 3: Start Metabase against the restored database

    Metabase will start, run any pending migrations (if restoring to a newer Metabase version), and be fully functional with all dashboards, users, and settings from the backup point.

    Step 4: Verify the restore

    bash
    

    <h1 class="text-4xl font-bold mb-6 text-slate-900">Check that Metabase starts successfully</h1> curl http://localhost:3001/api/health

    <h1 class="text-4xl font-bold mb-6 text-slate-900">Verify dashboards are present</h1> curl -H "x-api-key: $API_KEY" http://localhost:3001/api/dashboard | jq '.[].name'

    <h1 class="text-4xl font-bold mb-6 text-slate-900">Verify database connections are present (will need to re-enter passwords)</h1> curl -H "x-api-key: $API_KEY" http://localhost:3001/api/database | jq '.[].name'

    Step 5: Re-enter database connection passwords

    Database connection passwords are stored encrypted in the application database, encrypted with a key derived from the installation. After a restore, connection passwords may need to be re-entered in Admin → Databases.

    Partial Restore (Serialization)

    Use serialization import to restore specific dashboards or questions without a full database restore:

    bash
    

    <h1 class="text-4xl font-bold mb-6 text-slate-900">Import specific collections from a serialization export</h1> java -jar /app/metabase.jar import /path/to/serialization-export

    This adds or updates content in the running instance without affecting users, permissions, or other configuration.

    Point-in-Time Recovery (RDS)

    RDS PITR lets you restore to any second within the backup retention window:

    bash
    

    <h1 class="text-4xl font-bold mb-6 text-slate-900">AWS CLI: restore to a specific point in time</h1> aws rds restore-db-instance-to-point-in-time \ --source-db-instance-identifier metabase-app-db \ --target-db-instance-identifier metabase-app-db-restored \ --restore-time 2024-01-15T14:30:00Z

    After the restored instance is available (typically 10–30 minutes), update MB_DB_HOST in your Metabase configuration to point to the restored instance.

    ---

    Testing Your Backups

    Backups that have never been tested are unreliable. Run a monthly restore test:

    bash
    

    #!/bin/bash <h1 class="text-4xl font-bold mb-6 text-slate-900">test-restore.sh — run monthly</h1>

    BACKUP_FILE="s3://your-backup-bucket/metabase/latest.dump" TEST_DB="metabase_restore_test"

    echo "Downloading backup..." aws s3 cp "$BACKUP_FILE" /tmp/test-restore.dump

    echo "Creating test database..." psql -h test-db-host -U postgres -c "DROP DATABASE IF EXISTS $TEST_DB;" psql -h test-db-host -U postgres -c "CREATE DATABASE $TEST_DB;"

    echo "Restoring backup..." pg_restore \ -h test-db-host \ -U postgres \ -d "$TEST_DB" \ --no-owner \ /tmp/test-restore.dump

    echo "Starting test Metabase instance..." docker run -d \ --name metabase-restore-test \ -e MB_DB_TYPE=postgres \ -e MB_DB_HOST=test-db-host \ -e MB_DB_DBNAME="$TEST_DB" \ -e MB_DB_USER=postgres \ -e MB_DB_PASS="$TEST_DB_PASS" \ -p 3099:3000 \ metabase/metabase:v0.50.0

    echo "Waiting for startup..." sleep 90

    echo "Verifying restore..." STATUS=$(curl -s http://localhost:3099/api/health | jq -r '.status') if [ "$STATUS" = "ok" ]; then echo "āœ“ Restore test passed" else echo "āœ— Restore test FAILED" exit 1 fi

    echo "Cleaning up..." docker stop metabase-restore-test docker rm metabase-restore-test psql -h test-db-host -U postgres -c "DROP DATABASE $TEST_DB;"

    ---

    Summary

    Metabase backup means backing up its PostgreSQL application database — everything else (analytics data, Docker images) is managed separately. Use daily automated pg_dump backups uploaded to object storage, combined with point-in-time recovery for production RDS deployments. Supplement with serialization exports committed to git for an auditable, human-readable configuration history. Test restores monthly. After a restore, database connection passwords may need to be re-entered in the admin panel. A complete restore from backup to a functional Metabase instance takes under 30 minutes with a current backup and a tested runbook.