# How to self-host Metabase on a VPS: Docker, PostgreSQL and a read-only user

> Self-host Metabase on a VPS with Docker, a PostgreSQL app database, HTTPS, a read-only database user, memory settings and backups, or use ZoPanel.

Source: https://zopanel.net/blog/self-host-metabase  
Updated: 2026-10-21

## Key takeaways

- Metabase's open-source edition is free under the AGPL; paid plans start at $100 a month for 5 users at the time of writing.
- Metabase is a Java app: plan 2 GB of RAM and set the heap with JAVA_OPTS, about 1 GB on a 2 GB server.
- Store Metabase's own data in PostgreSQL in production; the default H2 file is for trying it out.
- Connect each data source with a read-only database user that only has SELECT.

**To self-host Metabase, run the official `metabase/metabase` Docker image with a PostgreSQL database for its own application data, publish port 3000 on `127.0.0.1` only behind nginx with HTTPS, and set the Java heap with `JAVA_OPTS` (about `-Xmx1g` on a 2 GB VPS).** Then connect each database you want to report on with a dedicated **read-only user**, never the application's own account. On a ZoPanel server, Metabase installs in one click from the App Store with SSL and a setup lock, but it cannot read databases on that same server, so it is usually installed on a second server.

This guide covers Metabase's licence and costs, sizing, the manual Docker install, the read-only user, the ZoPanel route, backups and updates.

## What is Metabase?

[Metabase](https://github.com/metabase/metabase) is a business intelligence tool: you connect it to MySQL, MariaDB, PostgreSQL and many other databases, then build questions, charts and dashboards that people without SQL skills can filter and share. It is a Java application.

**Licence.** According to Metabase's [licence file](https://github.com/metabase/metabase/blob/master/LICENSE.txt), code outside the `enterprise` directory is under the GNU AGPL, and the `metabase/metabase` Docker image is released under the AGPL. The `metabase/metabase-enterprise` image is under Metabase's commercial licence.

**Versions.** At the time of writing (October 2026) the latest stable release is [v0.64.1](https://github.com/metabase/metabase/releases) from 7 October 2026. Metabase keeps patching the previous line too: v0.63.20 came out on 19 October 2026.

## Self-hosted Metabase vs Metabase's paid plans

Prices from [metabase.com/pricing](https://www.metabase.com/pricing/) at the time of writing (October 2026):

| Plan | Price | Users included | Extra users |
| --- | --- | --- | --- |
| Open Source (self-hosted) | Free | Unlimited | None |
| Starter | $100/month or $1,080/year | 5 | $6/user/month |
| Pro | $575/month or $6,210/year | 10 | $12/user/month |
| Enterprise | From $20,000/year | Custom | Custom |

The open-source edition covers questions, dashboards and the SQL editor for unlimited users. The paid plans add support and extra features such as more granular permissions and embedding options, with cloud or self-hosted deployment depending on the plan; check the pricing page for the current split. For a small team that only needs internal dashboards, self-hosting the open-source edition is the obvious saving.

## How much RAM does Metabase need?

Metabase's [memory troubleshooting guide](https://www.metabase.com/docs/latest/troubleshooting-guide/running) says the JVM uses about a quarter of the machine's RAM by default, that leaving 1 to 2 GB for other processes is generally enough, and gives examples: `-Xmx1g` on a 2 GB machine, `-Xmx2g` on a 4 GB one. Its [production guide](https://www.metabase.com/learn/metabase-basics/administration/administration-and-operation/metabase-in-production) sizes 40 concurrent users at 3 CPU cores and 5 GB of RAM.

In practice: **2 GB of RAM** for a small team, 4 GB if PostgreSQL and other services share the server or many people use dashboards at the same time. ZoPanel runs it with a 1536 MB memory limit.

## Option 1: Self-host Metabase by hand with Docker

For a VPS without a panel. Install Docker Engine from [Docker's repository](https://docs.docker.com/engine/install/ubuntu/) (commands in our [n8n guide](/blog/self-host-n8n)).

### Step 1: Compose file with a PostgreSQL application database

Metabase's [Docker documentation](https://www.metabase.com/docs/latest/installation-and-operation/running-metabase-on-docker) says that to run Metabase in production "you'll need to store your application data in a production-ready database". The default embedded H2 file is fine for a trial.

```bash
sudo mkdir -p /opt/metabase && cd /opt/metabase
sudo nano compose.yaml
```

```yaml
services:
  metabase-db:
    image: postgres:17
    restart: unless-stopped
    environment:
      POSTGRES_DB: metabaseappdb
      POSTGRES_USER: metabase
      POSTGRES_PASSWORD: change-this-password
    volumes:
      - ./pgdata:/var/lib/postgresql/data

  metabase:
    image: metabase/metabase:v0.64.1
    restart: unless-stopped
    depends_on:
      - metabase-db
    ports:
      - "127.0.0.1:3000:3000"
    environment:
      MB_DB_TYPE: postgres
      MB_DB_DBNAME: metabaseappdb
      MB_DB_PORT: 5432
      MB_DB_USER: metabase
      MB_DB_PASS: change-this-password
      MB_DB_HOST: metabase-db
      JAVA_OPTS: "-Xmx1g"
```

```bash
sudo docker compose up -d
sudo docker compose logs -f metabase
```

The first start takes a minute or two while Metabase creates its tables.

### Step 2: nginx and HTTPS

```nginx
server {
    listen 80;
    server_name bi.example.com;

    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_read_timeout 300s;
    }
}
```

Enable the site and run `sudo certbot --nginx -d bi.example.com`. Open the domain at once and complete **Let's get started**: the first account becomes the administrator. Then, under **Admin → Settings → General**, set **Site URL** to `https://bi.example.com`, and under **Settings → Email** add SMTP so invitations and dashboard subscriptions work.

## Give Metabase a read-only database user

Metabase only needs to read. If its saved password leaks, or someone writes a careless native query, a read-only user limits the damage.

**MySQL or MariaDB** (replace the IP with the Metabase server's address, or `localhost` if both run on the same machine):

```sql
CREATE USER 'metabase_ro'@'203.0.113.10' IDENTIFIED BY 'a-long-random-password';
GRANT SELECT ON shop.* TO 'metabase_ro'@'203.0.113.10';
```

**PostgreSQL:**

```sql
CREATE ROLE metabase_ro LOGIN PASSWORD 'a-long-random-password';
GRANT CONNECT ON DATABASE shop TO metabase_ro;
\c shop
GRANT USAGE ON SCHEMA public TO metabase_ro;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO metabase_ro;
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO metabase_ro;
```

The last line covers tables created later. Allow only the Metabase server's IP in the firewall, and add the database under **Admin → Databases → Add a database**.

**If the database lives on a ZoPanel server**, the [Databases documentation](/docs/databases) gives two building blocks. The **Database users** card (MariaDB) creates extra users with the privileges you choose, such as `SELECT` only for reports; these users connect from the server itself. **Remote access** opens a database to listed IP addresses (up to 20 entries, never "anywhere") and shows a **Remote connection string** for the database's own user, which has full privileges on that database. For a remote Metabase with read-only rights, the server owner can open **Remote access** for the Metabase server's IP, then create a `SELECT`-only user for that IP as database root, as shown above.

## Option 2: Metabase with ZoPanel (one click)

On a ZoPanel server, Metabase is in the **App Store** ([full guide](/docs/app-metabase)):

1. Open **App Store**; click **Install Docker** first if asked.
2. On the **Metabase** card, click **Install**. Enter the **Domain** (for example `bi.example.com`); administrators choose the **Owner**. Leave **Free SSL (Let's Encrypt)** on.
3. Follow the task log until `Metabase is available at http://<domain>`.

**The setup lock.** The first person to open Metabase becomes its administrator, so visitors see "This app is being set up" until you finish:

1. On the **Docker** tab, wait for `running`, then click **Open the app (only for me)** in the **Setup lock is on** card. The first start takes one to two minutes.
2. Click **Let's get started**, enter your name, email, company and password, answer the usage question, and add a database or choose **I'll add my data later**.
3. Back on the **Docker** tab, click **Setup finished — open to everyone**.

**What to know before choosing this route:**

| Data source | Reachable from ZoPanel's Metabase? |
| --- | --- |
| Databases on other servers, on public addresses | Yes |
| Managed cloud databases with a public endpoint | Yes |
| MariaDB or PostgreSQL on the same server | No, whatever host you type |
| Private networks and VPC-only endpoints | No |

Containers are cut off from the server's database ports by design, so an abused app cannot reach other customers' data. The usual setup is Metabase on a second ZoPanel server, connected to the first through **Remote access**. ZoPanel's Metabase keeps its own data in the H2 file (`MB_DB_FILE`), uses the 0.63 release line, and runs with a 1536 MB memory limit. Because H2 is less robust than PostgreSQL, set a backup **Schedule**.

## Backing up Metabase

Your reporting data stays in the source databases. What you back up is Metabase's **application database**: users, questions, dashboards, collections and saved connections.

- **PostgreSQL app database:** `sudo docker compose exec -T metabase-db pg_dump -U metabase metabaseappdb > /root/metabase-$(date +%F).sql`
- **H2 file:** Metabase's [backup guide](https://www.metabase.com/docs/latest/installation-and-operation/backing-up-metabase-application-data) says to stop Metabase, copy `metabase.db.mv.db`, then start it again.
- **ZoPanel:** app data is not part of account backups. Use **Back up now** on the **Backups** card of the **Docker** tab, and a daily or weekly **Schedule** as administrator. The container is paused for a few seconds so the H2 file is consistent.

Copy backups off the server and encrypt them: they contain the passwords of every saved database connection. See [website backup strategy](/blog/website-backup-strategy).

## Updating Metabase

1. Back up the application database. Metabase migrates it on start, and a downgrade is not supported.
2. Change the image tag, then `sudo docker compose pull && sudo docker compose up -d`.
3. Expect one to two minutes of downtime while it starts.

On ZoPanel, **Update to latest** brings the newest 0.63 patch; later lines arrive with ZoPanel updates.

## Conclusion

Self-hosting Metabase is mostly about three settings: a real application database, a sensible Java heap and read-only access to your data. Get those right and the open-source edition gives a small team the dashboards a paid plan would, without per-user fees. If you want Metabase and your other apps on servers with SSL, isolation and app backups already handled, start with ZoPanel's Free plan and see the [database features](/features/databases):

```bash
curl -fsSL https://get.zopanel.net | sudo bash
```

## Frequently asked questions

### Is Metabase free to self-host?

Yes. The open-source edition is free under the AGPL with unlimited users. Paid plans add hosting, support and extra features, starting at $100 a month for 5 users at the time of writing.

### How much RAM does Metabase need?

Plan at least 2 GB and set `-Xmx1g`, following Metabase's guidance to leave 1 to 2 GB for other processes. Metabase sizes 40 concurrent users at about 3 CPU cores and 5 GB of RAM.

### Is the default H2 database OK for production?

Metabase recommends a production-ready application database such as PostgreSQL or MySQL. H2 is fine for a trial or a very small install with regular backups; migrate before you depend on it.

### Can Metabase change data in my database?

Metabase is built for reading, but a native SQL query runs with whatever rights the connection user has. Use a user with `SELECT` only, and control who may write SQL under **Admin → Permissions**.

### Does Metabase copy my data?

No. It stores questions, dashboards and connection settings in its application database, and queries the source database when a question runs. Cached results, if you turn caching on, are the exception.
