Migrating Metabase from H2 to PostgreSQL database: A step-by-step approach to overcoming challenges
Context
Recently, I had a task to migrate a development Metabase instance used internally by the dev team. The current Metabase was running on Kubernetes with a single node and using the default H2 database engine.
If you haven’t heard about H2 before, here’s a quick introduction:
H2 is a lightweight, fast, open-source relational database management system (RDBMS) written in Java. It supports in-memory and disk-based storage modes and is often used for development, testing, and small-scale applications. H2 is fully compatible with JDBC and SQL standards and can be embedded in Java applications or run as a standalone database. It also provides a web-based console for easy management.
Despite its advantages, H2 has significant drawbacks, mainly related to scalability and reliability. To make Metabase production-ready, we needed to migrate it to PostgreSQL, a more robust and reliable database engine. H2 stores its data as a file in a fixed Persistent Volume (PV).
Metabase provides an official migration guide here, which is quite detailed. However, I wanted to document the process for myself to reflect on the challenges I faced and, hopefully, help others encountering the same issues.
Migration Challenges and Solutions
Following the official documentation, the migration involves these steps:
- Confirm that you can connect to your target application database
- Back up your H2 application database
- Stop the existing Metabase container
- Download the JAR
- Run the migration command
- Start a new Docker container that uses the new app db
- Remove the old container that was using the H2 database
To simplify, my steps were:
- Extract the database file from the Kubernetes Persistent Volume.
- Download the necessary tools and the Metabase JAR file.
- Run the migration command.
- Start a new Metabase instance with PostgreSQL as the database engine.
First Challenge: Copying the Database File from the Pod
If someone asks you to retrieve a file from a pod, your first thought is probably to use the kubectl cp command. That was my initial approach too.
However, the database file was around 40MB, and every time I ran the command, it would only complete about 90% before getting interrupted.
kubectl cp <namespace>/<pod-name>:<path-in-pod> <local-path>
If a corrupted database file is migrated to the new database, the process will fail.
The download failure could be due to:
- An unstable VPN connection.
- The file being too large for kubectl cp to handle reliably.
After multiple failed attempts, I found a better solution: hosting a temporary Python web server inside the pod to serve the file.
# Step 1: Access to source metabase pod using kubectl exec
kubectl exec -it <metabase-pod-name> -- /bin/sh
# Step 2: Install Python (if not already installed)
apt update && apt install -y python3
# Step 3: Host a simple python web server
cd /path/to/metabase-folder
python3 -m http.server 8080 --bind 0.0.0.0
If you have direct access to the pod, the web server will be available at pod-ip:8080, allowing you to download the database file via a browser.
If you don’t have direct access, you can create a simple NodePort or LoadBalancer service to expose it externally.
✅ With this approach, the download was significantly faster and completed without interruptions.
Second Challenge: Matching Versions is CRUCIAL!
Now that I had the H2 database file, I needed to install dependencies and download metabase.jar to run the migration.
One key lesson: Version mismatches can break everything. Here are three important versions to check:
- Java version:
- Java must be version 21 or higher, as older versions are no longer supported.
- I ran into migration failures before realizing this! 😭
- Check your Java version with:
java -version
- More details: Java version requirements.
- PostgreSQL version:
- The target PostgreSQL version must be 12 or higher.
- I recommend using the latest LTS version.
- More details: Supported databases.
- Metabase version:
- The Metabase versions on the source (H2) and destination (PostgreSQL) must be identical.
- If versions don’t match, the migration might fail or cause issues later.
- Find the current Metabase version in the Docker image tag.
- Download the correct JAR version
https://downloads.metabase.com/<VERSION>/metabase.jar
- Example:
https://downloads.metabase.com/v0.53.5/metabase.jar
Now that I had:
- ✅ The source database file
- ✅ A PostgreSQL destination database
- ✅ The correct Java environment
- ✅ The matching Metabase JAR version
I could finally run the migration:
export MB_DB_TYPE=postgres
export MB_DB_CONNECTION_URI="jdbc:postgresql://<host>:5432/metabase?user=<username>&password=<password>"
java --add-opens java.base/java.nio=ALL-UNNAMED -jar metabase.jar load-from-h2 /path/to/metabase.db # do not include .mv.db
The process should complete within a few minutes—unless you use mismatched Metabase versions, like I did 🤣.
Third challenge: Some random errors
I was using Metabase 0.47.2, and during migration, I ran into foreign key constraint errors:
org.postgresql.util.PSQLException: ERROR: insert or update on table "report_dashboardcard" violates foreign key constraint "fk_report_dashboardcard_ref_dashboard_tab_id"
Detail: Key (dashboard_tab_id)=(1) is not present in table "dashboard_tab".
at org.postgresql.core.v3.QueryExecutorImpl.receiveErrorResponse(QueryExecutorImpl.java:2676)
at org.postgresql.core.v3.QueryExecutorImpl.processResults(QueryExecutorImpl.java:2366)
at org.postgresql.core.v3.QueryExecutorImpl.execute(QueryExecutorImpl.java:356)
at org.postgresql.core.v3.QueryExecutorImpl.execute(QueryExecutorImpl.java:316)
at org.postgresql.jdbc.PgConnection.executeTransactionCommand(PgConnection.java:928)
...
Command failed with exception: ERROR: insert or update on table "report_dashboardcard" violates foreign key constraint "fk_report_dashboardcard_ref_dashboard_tab_id"
Detail: Key (dashboard_tab_id)=(1) is not present in table "dashboard_tab".
I was confused, how could the schema be different if the Metabase versions matched?
After digging around, I found this post, which explained that Metabase 0.47.2 had a migration bug.
A user suggested upgrading to Metabase 0.47.4, so I downloaded that version and the migration completed successfully! 🎉
End
Thanks for reading! This post was mostly for my own reference, but I hope it helps others facing similar challenges.
If you run into technical issues and need help, feel free to reach out: https://hoangviet.io.vn
Hope you have a great day! 🚀
All rights reserved