# Deploying Metabase - Open-Source Business Intelligence Tool

Metabase is an open-source business intelligence (BI) tool that lets you explore data, build dashboards, and share reports from a web browser. Teams often self-host it because it connects to common databases such as PostgreSQL, MySQL, MariaDB, and SQL Server. This guide deploys Metabase on a Linux server using Docker Compose with a PostgreSQL backend and Traefik providing automatic HTTPS, covering directory and environment setup, deployment, and the initial administrator setup through the web interface. By the end, you'll have Metabase running securely at your domain, ready to connect data sources and build dashboards, following self-hosted business intelligence practices documented in [**Vultr Docs**](https://docs.vultr.com/how-to-deploy-metabase-open-source-business-intelligence-tool).

* * *

## Set Up the Project Directory and Environment Variables

Metabase stores its application data, such as users, dashboards, and saved questions, in PostgreSQL. This section creates the project directory and the environment file that the Compose stack shares.

**1\. Create the project directory and move into it:**

```console
mkdir ~/metabase
cd ~/metabase
```

**2\. Generate a random key that Metabase uses to encrypt stored data-source credentials:**

```console
openssl rand -base64 32
```

Save the output for use in the environment file.

**3\. Create the environment file:**

```console
nano .env
```

Add the following configuration. Replace `YOUR_DB_PASSWORD` with a strong password, `YOUR_ENCRYPTION_KEY` with the output from the previous `openssl` command, `metabase.example.com` with your domain name, and `ADMIN_EMAIL` with a deliverable email address for Let's Encrypt renewal notices.

```ini
POSTGRES_DB=metabaseappdb
POSTGRES_USER=metabase
POSTGRES_PASSWORD=YOUR_DB_PASSWORD
MB_ENCRYPTION_SECRET_KEY=YOUR_ENCRYPTION_KEY
MB_SITE_URL=https://metabase.example.com
DOMAIN=metabase.example.com
ACME_EMAIL=ADMIN_EMAIL
```

Save and close the file.

* * *

## Deploy with Docker Compose

The deployment stack consists of Traefik for reverse proxy and certificate management, plus the Metabase and PostgreSQL containers on a shared bridge network. This configuration uses the [official Metabase container image](https://hub.docker.com/r/metabase/metabase) version `v0.50.21`.

**1\. Create the Docker Compose manifest:**

```console
nano docker-compose.yml
```

```yaml
services:
  postgres:
    image: postgres:16
    container_name: metabase-postgres
    restart: unless-stopped
    environment:
      POSTGRES_DB: ${POSTGRES_DB}
      POSTGRES_USER: ${POSTGRES_USER}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
    volumes:
      - postgres-data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
      interval: 10s
      timeout: 5s
      retries: 5
    mem_limit: 1g
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"
    networks:
      - metabase-network

  metabase:
    image: metabase/metabase:v0.50.21
    container_name: metabase
    restart: unless-stopped
    depends_on:
      postgres:
        condition: service_healthy
    environment:
      MB_DB_TYPE: postgres
      MB_DB_DBNAME: ${POSTGRES_DB}
      MB_DB_PORT: 5432
      MB_DB_USER: ${POSTGRES_USER}
      MB_DB_PASS: ${POSTGRES_PASSWORD}
      MB_DB_HOST: postgres
      MB_ENCRYPTION_SECRET_KEY: ${MB_ENCRYPTION_SECRET_KEY}
      MB_SITE_URL: ${MB_SITE_URL}
    healthcheck:
      test: ["CMD", "curl", "--fail", "http://localhost:3000/api/health"]
      interval: 30s
      timeout: 10s
      retries: 5
      start_period: 120s
    mem_limit: 2g
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"
    networks:
      - metabase-network
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.metabase.rule=Host(`${DOMAIN}`)"
      - "traefik.http.routers.metabase.entrypoints=websecure"
      - "traefik.http.routers.metabase.tls.certresolver=letsencrypt"
      - "traefik.http.services.metabase.loadbalancer.server.port=3000"

  traefik:
    image: traefik:v3.7.10
    container_name: traefik
    restart: unless-stopped
    command:
      - "--providers.docker=true"
      - "--providers.docker.exposedbydefault=false"
      - "--entrypoints.web.address=:80"
      - "--entrypoints.web.http.redirections.entrypoint.to=websecure"
      - "--entrypoints.web.http.redirections.entrypoint.scheme=https"
      - "--entrypoints.websecure.address=:443"
      - "--certificatesresolvers.letsencrypt.acme.email=${ACME_EMAIL}"
      - "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json"
      - "--certificatesresolvers.letsencrypt.acme.tlschallenge=true"
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./letsencrypt:/letsencrypt
    networks:
      - metabase-network

volumes:
  postgres-data:

networks:
  metabase-network:
    driver: bridge
```

Save and close the file. `postgres` runs PostgreSQL 16 as the database storing Metabase's users, dashboards, and saved questions. `metabase` runs the application server, starting only after PostgreSQL reports healthy, with Traefik labels registering it for automatic HTTPS on the configured `DOMAIN`. `traefik` handles reverse proxying and TLS termination on ports 80 and 443. `mem_limit` and `logging` cap each container's memory and rotate its logs, preventing either from exhausting the server's resources.

**2\. Launch the containers:**

```console
docker compose up -d
```

**3\. Verify that the services are running:**

```console
docker compose ps -a
```

The output displays all three containers in an `Up` state, with a healthy status shown for `metabase-postgres` and `metabase` once their health checks pass.

**4\. Query the Metabase health endpoint through your domain:**

```console
curl https://metabase.example.com/api/health
```

Your output should be similar to the one below:

```plaintext
{"status":"ok"}
```

**5\. View the service logs to confirm Metabase loaded the configuration successfully:**

```console
docker compose logs -f metabase
```

The output displays PostgreSQL accepting connections and Metabase completing its startup migrations, which take one to two minutes. Press Ctrl+C to stop following the logs once the service reports that it has started.

* * *

## Access Metabase and Complete the Initial Setup

Open Metabase in a browser and complete the first-run wizard.

1.  Open your domain over HTTPS in a browser. Replace `metabase.example.com` with your domain:
    
    ```plaintext
    https://metabase.example.com
    ```
    
2.  Select your preferred language.
    
3.  Create the initial administrator account by entering your name, email address, and password.
    
4.  When prompted to add your first data source, configure one now or skip and add it later.
    

Setup is now complete, and Metabase is ready to use. Any databases you connect through the interface stay separate from the PostgreSQL container, which stores only Metabase's own metadata.

* * *

## Next Steps

Metabase is live with Traefik-managed HTTPS and a PostgreSQL-backed metadata store. From here you can:

*   Connect your production databases and start building dashboards and saved questions
    
*   Configure user groups and permissions to control who can see which data
    
*   Set up scheduled email or Slack reports (pulses) so dashboards reach stakeholders automatically
    

For the full guide with additional tips, visit the original article on [**Vultr Docs**](https://docs.vultr.com/how-to-deploy-metabase-open-source-business-intelligence-tool).
