Serialization: preloading dashboards in a new Metabase instance

How to use Metabase's serialization feature to copy questions, dashboards, collections, settings, and more from one Metabase instance to a new Metabase instance.

Metabase serialization

Serialization is only available on Pro and Enterprise plans (only on self-hosted plans).

Many customers on paid plans use Metabase in a multi-tenant environment that requires uploading a predefined set of questions or dashboards, either to set up a new Metabase instance, or a new database connection. This article will cover how to:

  1. Create a default set of questions and dashboards.
  2. Export those dashboards.
  3. Re-import those dashboards to a new instance.

Specifically, we’ll use the export and import commands in Metabase’s serialization feature to perform steps two and three, plus a little bit of manual curation of the exported files.

We’ll use Docker to run our Metabase environments, and use open source PostgresSQL for our application databases. We don’t recommend using the default H2 database for production.

The plan

We’ll create a Metabase instance (our origin environment), create a dashboard, and import that dashboard into a new Metabase instance (our target environment). Here’s the plan:

  1. Create a dedicated network called metanet.
  2. Spin up two instances of Metabase: origin and target.
  3. Create dashboards and collections in the origin environment.
  4. Export the data from the origin environment.
  5. Import the origin export into the target environment.
  6. Verify that our dashboard and collection is loaded in the target environment.


You’ll need to have Docker installed on your machine.

Step 1 - Create a dedicated network

To create a dedicated network called “metanet”, run the following command from your terminal of choice:

docker network create metanet

You can confirm the network was created with:

docker network ls

The network will have a local scope and a bridge driver.

Step 2 - Spin up two instances of Metabase

Spin up two Metabase environments called origin and target (though you can name these environments whatever you like). Note that we use --rm -d when creating these Docker containers so they both get removed when you stop them and run in the background. Feel free to change those flags to modify that behavior.

Origin environment

Create the Postgres database:

docker run --rm -d --name postgres \
    -p 5433:5432 \
    -e POSTGRES_USER=metabase \
    -e POSTGRES_PASSWORD=knockknock \
    --network metanet \

Create the Metabase origin instance, and connect it to Postgres database we just created:

docker run --rm -d --name metabase-origin \
    -p 5001:3000 \
    -e MB_DB_TYPE=postgres \
    -e MB_DB_DBNAME=metabase \
    -e MB_DB_PORT=5432 \
    -e MB_DB_USER=metabase \
    -e MB_DB_PASS=knockknock \
    -e MB_DB_HOST=postgres \
    --network metanet \

You can check the container’s logs to view the container’s progress:

docker logs metabase-origin

Once you see the line that contains “Metabase initialization COMPLETE”, you can open a browser to http://localhost:5001 to view your Metabase instance.

Target environment

Setting up a target environment is similar. On our metanet network, we’ll set up a Postgres database to serve as our application database, then spin up another instance of Metabase in another Docker container.

Note the changes to:

  • ports for both Postgres (5434) and the Metabase server (5002)
  • Instance names: postgres-target and metabase-target

Application database:

docker run --rm -d --name postgres-target \
    -p 5434:5432 \
    -e POSTGRES_USER=metabase \
    -e POSTGRES_PASSWORD=knockknock \
    --network metanet postgres:12

Metabase instance:

docker run --rm -d --name metabase-target \
    -p 5002:3000 \
    -e MB_DB_TYPE=postgres \
    -e MB_DB_DBNAME=metabase \
    -e MB_DB_PORT=5432 \
    -e MB_DB_USER=metabase \
    -e MB_DB_PASS=knockknock \
    -e MB_DB_HOST=postgres-target \
    --network metanet \

After our Metabase instances complete their initialization (patience, this could take a minute or two), we should now have two Metabase environments up and running:

  • metabase-origin at http://localhost:5001
  • metabase-target at http://localhost:5002

Add users to our metabase-origin environment

Let’s add one Admin account, and two basic users to our metabase-origin instance.

You can add users to your Metabase environment manually (i.e., in the Metabase application), but here’s a quick bash script that creates an Admin user (the initial user) and two basic users:




echo "⌚︎ Waiting for Metabase to start"
while (! curl -s -m 5 http://${METABASE_HOST}:${METABASE_PORT}/api/session/properties -o /dev/null); do sleep 5; done

echo "😎 Creating admin user"

SETUP_TOKEN=$(curl -s -m 5 -X GET \
    -H "Content-Type: application/json" \
    http://${METABASE_HOST}:${METABASE_PORT}/api/session/properties \
    | jq -r '.["setup-token"]'

MB_TOKEN=$(curl -s -X POST \
    -H "Content-type: application/json" \
    http://${METABASE_HOST}:${METABASE_PORT}/api/setup \
    -d '{
    "token": "'${SETUP_TOKEN}'",
    "user": {
        "email": "'${ADMIN_EMAIL}'",
        "first_name": "Metabase",
        "last_name": "Admin",
        "password": "'${ADMIN_PASSWORD}'"
    "prefs": {
        "allow_tracking": false,
        "site_name": "Metawhat"
}' | jq -r '.id')

echo -e "\n👥 Creating some basic users: "
curl -s "http://${METABASE_HOST}:${METABASE_PORT}/api/user" \
    -H 'Content-Type: application/json' \
    -H "X-Metabase-Session: ${MB_TOKEN}" \
    -d '{"first_name":"Basic","last_name":"User","email":"","login_attributes":{"region_filter":"WA"},"password":"'${ADMIN_PASSWORD}'"}'

curl -s "http://${METABASE_HOST}:${METABASE_PORT}/api/user" \
    -H 'Content-Type: application/json' \
    -H "X-Metabase-Session: ${MB_TOKEN}" \
    -d '{"first_name":"Basic 2","last_name":"User","email":"","login_attributes":{"region_filter":"CA"},"password":"'${ADMIN_PASSWORD}'"}'

echo -e "\n👥 Basic users created!"

You’ll need to have jq installed to handle the JSON in this script. Save the above code as, and make it executable:

chmod +x

Then run:

MB_HOSTNAME=localhost MB_PORT=5001 ./

With your metabase-origin instance up, and your users created, open up http://localhost:5001] and sign in as the admin user you created. The user ID is admin@metabase.local and the password is Metapass123.

You should see a fresh instance of Metabase (figure 1).

<em>Fig. 1</em>. A fresh instance of Metabase.
Fig. 1. A fresh instance of Metabase.

Once you log in, activate your license key.

Step 3 - Create dashboards and collections in the origin environment

We’ll need some application data to export, so let’s create some dashboards using the Sample Database included with Metabase. Or rather, let’s let Metabase create some dashboards for us!

As shown in figure 2, in the Try These X-Rays Based On Your Data section, click on the card with a yellow lightning bolt that says something like A look at Products. Metabase will generate a set of questions for you that you can save as a dashboard.

<em>Fig. 2</em>. An X-ray of the Products table in the Sample Database included with Metabase.
Fig. 2. An X-ray of the Products table in the Sample Database included with Metabase.

Click on the Save this button, and Metabase will save the dashboard and its questions in a collection titled something like A look at Products.

This collection will be saved to a parent collection titled Automatically Generated Dashboards. You can find this collection by clicking on the Metabase logo in the upper left of the navigation bar to return to the home screen. From the home page, in the Our Analytics section, click on the Automatically Generated Dashboards section. From there you should see the collection A look at your Products table (figure 3).

<em>Fig. 3</em>. A collection titled <strong>A look at your Products table</strong>.
Fig. 3. A collection titled A look at your Products table.

Next, create a new collection. You can call it whatever you like; we’ll use the exciting name Default collection, and save it to the Our Analytics collection.

<em>Fig. 4</em>. Creating a new collection, titled <strong>Default Collection</strong>.
Fig. 4. Creating a new collection, titled Default Collection.

Then we’ll move the A look at Products collection to our newly created Default collection. On the A look at Products collection page, click on the ellipses and select Move.

Step 4 - Export from origin environment

Here’s where we actually start using Metabase’s serialization feature.

With our metabase-origin instance set up with some questions, now it’s time to export this data and import it into our metabase-target environment. That way we don’t have to manually recreate our Default Collection in the target environment.

Let’s first create a directory in our /tmp directory called metabase_data to store our export:

cd /tmp
mkdir metabase_data

Next, we’ll run the export command.

docker run --rm --name metabase-export \
    --network metanet \
    -e MB_DB_CONNECTION_URI="postgres://postgres:5432/metabase?user=metabase&password=knockknock" \
    -v "/tmp/metabase_data:/target" \
    metabase/metabase-enterprise:v1.47.2 "export /target"

This command creates a temporary metabase instance called metabase-export. This temporary Metabase will connect to the Postgres application database for the metabase-origin environment, and export the environment’s data.

If all goes well, after a few seconds you should see some output, followed by a message in your terminal that says “Finished running data migrations”.

To verify the export, cd into your directory: /tmp/metabase_data. You should see something like two directories and three YAML files:


The settings file contains a number of options that you can configure when setting up a new instance. It’ll look something like:

humanization-strategy: null
native-query-autocomplete-match-style: null
site-locale: null
report-timezone-short: null
report-timezone-long: null
application-name: null
enable-xrays: null
show-homepage-pin-message: null
source-address-header: null
enable-nested-queries: null
custom-geojson-enabled: null
start-of-week: null
custom-geojson: null
available-timezones: null
max-results-bare-rows: null
hide-embed-branding?: null
search-typeahead-enabled: null
enable-sandboxes?: null
application-font: null
available-locales: null
landing-page: null
enable-embedding: null
application-colors: null
application-logo-url: null
application-favicon-url: null
show-homepage-xrays: null
show-metabot: null
enable-whitelabeling?: null
show-homepage-data: null
site-name: Metawhat
application-font-files: null
loading-message: null
report-timezone: null
show-lighthouse-illustration: null
persisted-models-enabled: null
subscription-allowed-domains: null
breakout-bins-num: null
available-fonts: null
custom-formatting: null


This directory contains all of metadata settings for your connected databases. In this case, we only have the Sample Database included with Metabase.


In the collections directory are the collections, dashboards, and questions we set up. The eDuYBjvKEwhFg6QxtBziP_default_collection directory has sub-collections and other items. Each item is prefixed with a code to avoid naming collisions.

Here’s a look at a collection yaml file:

authority_level: null
description: null
archived: false
slug: default_collection
color: '#509EE3'
name: Default collection
personal_owner_id: null
type: null
parent_id: null
- model: Collection
  id: eDuYBjvKEwhFg6QxtBziP
  label: default_collection
entity_id: eDuYBjvKEwhFg6QxtBziP
namespace: null
created_at: '2023-09-22T18:49:33.32189Z'

Here’s a peek at an example question (called a card) titled Products per category:

description: null
archived: false
collection_position: null
- Sample Database
result_metadata: null
database_id: Sample Database
enable_embedding: false
collection_id: xRnHb-5UlMJXSPHMs3jON
query_type: query
name: Products per Category
creator_id: admin@metabase.local
made_public_by_id: null
embedding_params: null
cache_ttl: null
  type: query
  database: Sample Database
    - Sample Database
    - PUBLIC
    - - field
      - - Sample Database
        - PUBLIC
        - PRODUCTS
        - CATEGORY
      - null
    - - count
    - - desc
      - - aggregation
        - 0
parameter_mappings: []
- model: Card
  id: L5uw5g7XK9o3SJBbiPLQf
  label: products_per_category
display: row
entity_id: L5uw5g7XK9o3SJBbiPLQf
collection_preview: true
  - number
  - '#EF8C8C'
  - count
  column_settings: null
parameters: []
dataset: false
created_at: '2023-09-22T18:10:55.633897Z'

Step 5 - Import into target environment

You’ll need at least one admin account loaded into our metabase-target in order to upload a export. You can login via the app to create that user, or use the script we used above: just remember to change the MB_PORT to 5002, since that’s the port we assigned to our metabase-target environment. For example, cd into the directory where you saved your script, and run:

MB_HOSTNAME=localhost MB_PORT=5002 ./

We can upload all of these settings into the target environment, but let’s assume we only want to import our default collection.

Let’s copy our /tmp/metabase_data directory so we can keep the original contents and make changes to the copy.

cp -r /tmp/metabase_data /tmp/serialize_load

Change into our new directory:

cd /tmp/serialize_load

Since every Metabase instance includes the Sample Database, and we didn’t make any changes to the metadata, let’s delete the databases directory. From within the /tmp/serialize_load directory, run rm -r databases.

To verify the changes, you can run diff to see the changes between the original serialized_data directory, and the serialized_load directory you’ll use to import into the metabase-target environment:

diff -r metabase_data serialize_load

And you should see the following:

Only in metabase_data: databases

Now, with our /tmp/serialize_load directory set, we can run the import command to import the metadata into out target environment, metabase-target.

docker run --rm --name metabase-export \
    --network metanet \
    -e MB_DB_CONNECTION_URI="postgres://postgres-target:5432/metabase?user=metabase&password=knockknock" \
    -v "/tmp/serialize_load:/target" \
    metabase/metabase-enterprise:v1.47.2 "import /target"

Step 6 - Verify dashboard and collection in target environment

Now, if you log in to the target environment at http://localhost:5002, you should see our Default collection ready to go, containing our A look at your Products table collection.

And that’s it: you’ve preloaded a fresh instance of Metabase with a collection containing a dashboard full of questions!

Serialization limitations

Just note that serialization dumps do not contain certain data:

Other use cases for serialization

Using the serialization feature to export questions and dashboards opens up some cool possibilities, including:

  • Adding version control to questions and dashboards. You can check in the downloaded metadata to a repository, and manage changes to that data via version control software like git.
  • Setting up a staging environment for Metabase. You can play around with a staging environment until you’re happy with the changes, then export the metadata, and upload it to a production environment.

Play around with the serialization feature, and let us know how you’re using it on our on our forum.

Thanks for your feedback!

Get articles in your inbox every month