DEV Community

Cover image for Deploying Metabase - Open-Source Business Intelligence Tool
Sanskriti Harmukh for Vultr

Posted on with Aashish Chaurasiya Originally published at docs.vultr.com

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.


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:

$ mkdir ~/metabase
$ cd ~/metabase
Enter fullscreen mode Exit fullscreen mode

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

$ openssl rand -base64 32
Enter fullscreen mode Exit fullscreen mode

Save the output for use in the environment file.

3. Create the environment file:

$ nano .env
Enter fullscreen mode Exit fullscreen mode

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.

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
Enter fullscreen mode Exit fullscreen mode

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 version v0.50.21.

1. Create the Docker Compose manifest:

$ nano docker-compose.yml
Enter fullscreen mode Exit fullscreen mode
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
Enter fullscreen mode Exit fullscreen mode

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:

$ docker compose up -d
Enter fullscreen mode Exit fullscreen mode

3. Verify that the services are running:

$ docker compose ps -a
Enter fullscreen mode Exit fullscreen mode

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:

$ curl https://metabase.example.com/api/health
Enter fullscreen mode Exit fullscreen mode

Your output should be similar to the one below:

{"status":"ok"}
Enter fullscreen mode Exit fullscreen mode

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

$ docker compose logs -f metabase
Enter fullscreen mode Exit fullscreen mode

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:
   https://metabase.example.com
Enter fullscreen mode Exit fullscreen mode
  1. Select your preferred language.
  2. Create the initial administrator account by entering your name, email address, and password.
  3. 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.

Top comments (1)

Collapse
 
sinarezaei profile image
Sina Rezaei

I like that this guide doesn't stop at getting Metabase running. The health checks, memory limits, log rotation, and the separation between Metabase's application data and the databases it connects to are the details that make a deployment much more practical.

The PostgreSQL health check before starting Metabase is a small thing, but it saves you from a lot of confusing startup problems. Same with exposing only Traefik on 80/443 and letting it handle the HTTPS side.

I've seen plenty of Docker guides where docker compose up -d is basically the end of the story. Here, checking the health endpoint and logs afterwards makes the deployment feel much closer to something you'd actually want to maintain.

One thing I'd be interested in seeing in a follow-up is backup and restore for the PostgreSQL metadata volume. Getting the dashboard online is one thing, but being able to recover the Metabase state is where self-hosting gets really interesting.