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...
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 stored | Examples |
|---|---|
| Dashboard definitions | Layout, cards, filters, parameters |
| Question/card definitions | Queries, visualization settings |
| Collections | Folder structure, permissions |
| User accounts | Email, name, group memberships, login attributes |
| Permission groups | Group definitions and memberships |
| Database connections | Host, port, credentials (encrypted) |
| Instance settings | Site URL, auth config, embedding secret |
| Pulse/alert configs | Scheduled report settings |
What You Do Not Need to Back Up
- The Metabase Docker image or JAR file (pull from Docker Hub on restore)
---
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 included | Database backup | Serialization export |
|---|---|---|
| Dashboards and questions | ā | ā |
| Collections | ā | ā |
| Users and passwords | ā | ā |
| Database connections | ā (credentials encrypted) | ā |
| Permission groups | ā | ā |
| Instance settings | ā | Partial |
| Git history | ā | ā |
| Human-readable | No | Yes |
| Importable to different instance | With effort | Yes (designed for this) |
---
Backup Schedule Recommendations
| Environment | Database backup | Serialization export | Retention |
|---|---|---|---|
| Production | Daily automated + PITR (RDS) | On every change (git) | 30 days |
| Staging | Daily automated | On every change (git) | 7 days |
| Dev | Not required | On every change (git) | N/A |
---
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.