Self-Hosting Metabase on Linux VPS in Pakistan: The Open-Source Business Intelligence & Analytics Guide

Complete production guide to self-hosting Metabase on Linux VPS in Pakistan. Connect PostgreSQL, MySQL, and BigQuery to build interactive executive dashboards, automated Slack/email reporting, and eliminate expensive PowerBI/Tableau USD per-seat licensing fees.

Self-Hosting Metabase on Linux VPS in Pakistan: The Open-Source Business Intelligence & Analytics Guide

Modern organizations cannot navigate competitive markets without continuous access to operational telemetry. Whether tracking daily Gross Merchandise Value (GMV) across Shopify and WooCommerce stores, monitoring sprint velocity in software engineering agencies, or auditing loan disbursements in fintech startups, data-informed decision-making is the bedrock of digital business.

For years, enterprise business intelligence (BI) has been dominated by proprietary, closed-source SaaS platforms such as Microsoft Power BI Pro, Tableau, and Looker. However, for companies operating in Pakistan, proprietary cloud BI introduces massive recurring financial drains and operational roadblocks:

  • Per-Seat Foreign Currency Drain: Power BI Pro starts at $10 to $14 per user per month, while Tableau Creator and Explorer licenses scale from $42 to $75 per user per month. For a mid-sized software house or digital retail brand with 40 stakeholders—including project managers, client service leads, operations teams, and executives—BI licensing alone incurs $500 to $3,000 every single month in foreign currency.
  • Card Limits and Regulatory Taxation: Under State Bank of Pakistan (SBP) foreign exchange guidelines, corporate credit cards are constrained by stringent monthly spending ceilings. Furthermore, Pakistani corporate cards face 5% to 10% advance withholding taxes on foreign SaaS payments, accompanied by international processing markups and rupee volatility.
  • Data Residency and Security Exposure: Ingesting sensitive customer records, banking details, Pakistani National Identity Numbers (CNICs), and raw transactional databases into third-party, multi-tenant overseas clouds raises compliance friction under international data protection laws and corporate confidentiality non-disclosure agreements (NDAs).
  • Vendor Lock-in and Query Throttling: Closed BI suites impose restrictive data refresh limits, proprietary modeling layers (such as DAX or proprietary calc engines), and export caps designed to upsell enterprise tier upgrades.

Metabase offers the industry-standard open-source solution to these hurdles. With more than 35,000 GitHub stars, Metabase delivers clean, intuitive visualization interfaces, drag-and-drop visual query builders, native SQL editors, and real-time dashboard subscriptions.

By self-hosting Metabase on an independent, high-performance Linux VPS, Pakistani engineering and business teams gain unmetered user seats, zero per-user licensing fees, full data sovereignty, and direct sub-millisecond local network access.

This guide provides a comprehensive, production-grade architectural and deployment manual for running Metabase on Ubuntu Linux, configuring external PostgreSQL metadata persistence, tuning the Java Virtual Machine (JVM) heap memory, securing database connections, configuring Nginx reverse proxying with SSL, and scheduling automated corporate Slack and email reporting.


Metabase Architecture: How the Engine Operates

To operate Metabase reliably in production, systems engineers must understand its internal structural components and how analytical requests travel from the browser to operational databases.

+-----------------------------------------------------------------------+
|                    Browser / Client Dashboard View                    |
|                        (React Single Page App)                        |
+-----------------------------------+-----------------------------------+
                                    |
                                    v HTTPS (Port 443)
+-----------------------------------------------------------------------+
|                Nginx Reverse Proxy / SSL Termination                  |
|                 (Gzip, Buffering, WebSocket Headers)                  |
+-----------------------------------+-----------------------------------+
                                    |
                                    v HTTP (Port 3000)
+-----------------------------------------------------------------------+
|                       Metabase Application Server                     |
|                                                                       |
|  +-----------------------------------------------------------------+  |
|  |                 Clojure Runtime on OpenJDK JVM                 |  |
|  |   - Metabase Query Language (MBQL) Compiler                     |  |
|  |   - SQL Query Sanitizer & Parser                                |  |
|  |   - Connection Pooling (c3p0 / HikariCP)                        |  |
|  |   - In-Memory Query Result Caching Engine                       |  |
|  |   - Automated Pulse / Email / Slack Scheduler                   |  |
|  +-----------------------------------------------------------------+  |
+-------------------+-------------------------------+-------------------+
                    |                               |
    Application     |                               |   Analytical
    State Read/Write|                               |   Query Execution
                    v                               v
+-------------------------------+   +-----------------------------------+
|  Dedicated Application DB     |   |   External Target Databases       |
|  (PostgreSQL 16 Engine)       |   |   (Read-Only Data Warehouses)     |
|                               |   |                                   |
|   - Dashboards & Questions    |   |   - Production PostgreSQL / MySQL |
|   - Field Metadata & Types    |   |   - Google BigQuery / ClickHouse  |
|   - User RBAC & Collections   |   |   - Read Replicas & Data Lakes    |
|   - Saved Query History       |   |                                   |
+-------------------------------+   +-----------------------------------+

1. Clojure JVM Backend

Unlike lightweight Node.js or Python backend servers, Metabase is written in Clojure, a modern dialect of Lisp targeting the Java Virtual Machine (JVM). The JVM provides rock-solid multi-threaded concurrency, robust database driver management (JDBC), and enterprise-grade garbage collection. The Clojure backend translates visual drag-and-drop queries into Metabase Query Language (MBQL) and compiles them into dialect-specific SQL for connected databases.

2. React SPA Frontend

The user interface is a responsive, highly optimized React single-page application. It renders responsive SVG charts, time-series line graphs, heat maps, and pivot tables. It also supports granular drill-down interactions, permitting non-technical team members to filter revenue numbers by city (e.g., Karachi, Lahore, Islamabad, Faisalabad) without writing a line of SQL.

3. Application Metadata Storage: The Fatal H2 Pitfall

Metabase requires an underlying relational database to record its own operational state: user logins, role-based access control (RBAC), collection permissions, dashboard layouts, question definitions, query cache hashes, and scheduled report configurations.

By default, Metabase launches with an embedded file-based H2 database.

CRITICAL ARCHITECTURAL WARNING:
Never use the default embedded H2 database in production.
- If a container restarts or encounters an abrupt power loss, H2 database files frequently corrupt irreversibly.
- H2 locks files single-threadedly, causing query lockups when multiple analysts load dashboards simultaneously.
- When updating Docker container images, engineers who fail to mount host volumes permanently destroy all dashboards and user permissions.
- In production, Metabase MUST be configured with an external, persistent PostgreSQL or MySQL database engine.

Using a dedicated PostgreSQL 16 container as the Metabase application database ensures ACID compliance, automated backup pipelines, and rapid disaster recovery.


Infrastructure Sizing: Selecting the Right VPS Specification

Analytical workloads place unique demands on server hardware. While the Metabase web interface is lightweight, processing unindexed SQL joins across millions of records, generating visualization payloads, and compiling complex aggregations requires adequate RAM and fast storage I/O.

Deployment Scale Active Analysts Synced Databases Recommended VPS Specification Memory Allocation (JAVA_OPTS)
Starter / Small Agency 1 to 10 users 1 to 2 SQL databases 2 vCPU, 4 GB RAM, 60 GB NVMe -Xms1g -Xmx2g
Mid-Market / E-Commerce 10 to 40 users 3 to 6 databases 4 vCPU, 8 GB RAM, 120 GB NVMe -Xms2g -Xmx4g
Enterprise / Multi-Tenant BI 40 to 100+ users 6+ databases / Warehouses 8 vCPU, 16 GB to 32 GB RAM, 250 GB NVMe -Xms4g -Xmx12g

Understanding JVM Heap Memory Management

Because Metabase runs inside a Java Virtual Machine, the operating system kernel cannot dynamically size JVM memory on demand. If the JVM exhausts its pre-allocated heap while buffering large query results, it immediately crashes with:

java.lang.OutOfMemoryError: Java heap space

To prevent random process termination, you must explicitly declare initial (-Xms) and maximum (-Xmx) heap boundaries in your environment variables.

As a fundamental rule of systems engineering, never assign 100% of physical VPS RAM to -Xmx. Leave at least 25% to 35% of system memory unreserved for the host Linux OS kernel, the local PostgreSQL application database, page caches, and Nginx reverse proxy buffers. For an 8 GB VPS, allocating -Xms2g -Xmx4g provides an optimal operational balance.


Production Docker Compose Deployment

The cleanest, most maintainable method to deploy Metabase on a Linux VPS is via Docker Compose. This ensures process isolation, effortless version upgrades, automated restarts, and internal network containment.

Step 1: Base Operating System Preparation

Log in to your Ubuntu 24.04 or 22.04 LTS VPS instance as root or a user with sudo privileges. Update existing packages and install core system utilities:

# Update repository indexes and upgrade packages
sudo apt update && sudo apt upgrade -y

# Configure system timezone to Pakistan Standard Time (PKT)
sudo timedatectl set-timezone Asia/Karachi

# Install foundational utilities
sudo apt install -y curl wget git ufw htop fail2ban ca-certificates gnupg lsb-release

Step 2: Install Docker Engine and Docker Compose

Install official Docker packages from the upstream Docker repository:

# Add Docker official GPG key
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg

# Add Docker APT repository
echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
  $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

# Install Docker Engine, CLI, and Compose plugin
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

# Verify installation
docker --version
docker compose version

Step 3: Directory Structure and Environment Configuration

Create a dedicated directory structure for Metabase under /opt/metabase:

# Create directory structure
sudo mkdir -p /opt/metabase/postgres_data
sudo mkdir -p /opt/metabase/certs
sudo cd /opt/metabase

Create a secure .env file to store database credentials and JVM runtime parameters:

# Generate high-entropy passwords
DB_PASSWORD=$(openssl rand -base64 24)
MB_ENCRYPTION_KEY=$(openssl rand -base64 32)

# Write configuration to /opt/metabase/.env
cat <<EOF | sudo tee /opt/metabase/.env
# Application Database Settings
POSTGRES_DB=metabase_app_db
POSTGRES_USER=metabase_admin
POSTGRES_PASSWORD=${DB_PASSWORD}

# Metabase JVM & Runtime Parameters
JAVA_OPTS=-Xms2g -Xmx4g -XX:+UseG1GC -XX:MaxGCPauseMillis=200
MB_ENCRYPTION_SECRET_KEY=${MB_ENCRYPTION_KEY}
MB_SITE_URL=https://analytics.yourcompany.pk
EOF

Protect the .env file from unauthorized system inspection:

sudo chmod 600 /opt/metabase/.env

Step 4: Authoring the Docker Compose Specification

Create the production docker-compose.yml file:

# /opt/metabase/docker-compose.yml
services:
  metabase_db:
    image: postgres:16-alpine
    container_name: metabase_postgres_db
    restart: unless-stopped
    environment:
      POSTGRES_DB: ${POSTGRES_DB}
      POSTGRES_USER: ${POSTGRES_USER}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
    volumes:
      - /opt/metabase/postgres_data:/var/lib/postgresql/data
    networks:
      - metabase_internal_net
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
      interval: 10s
      timeout: 5s
      retries: 5

  metabase_app:
    image: metabase/metabase:latest
    container_name: metabase_core_app
    restart: unless-stopped
    depends_on:
      metabase_db:
        condition: service_healthy
    environment:
      # Connect Metabase to external PostgreSQL metadata engine
      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: metabase_db
      
      # Security and performance parameters
      JAVA_OPTS: ${JAVA_OPTS}
      MB_ENCRYPTION_SECRET_KEY: ${MB_ENCRYPTION_SECRET_KEY}
      MB_SITE_URL: ${MB_SITE_URL}
      MB_JETTY_PORT: 3000
    ports:
      # Bind exclusively to 127.0.0.1 so the port is not exposed publicly
      - "127.0.0.1:3000:3000"
    networks:
      - metabase_internal_net

networks:
  metabase_internal_net:
    driver: bridge

Step 5: Launching the Stack

Start the Metabase application and PostgreSQL database containers in detached mode:

cd /opt/metabase
sudo docker compose up -d

Monitor the startup log sequence to verify database schema creation and JVM initialization:

sudo docker compose logs -f metabase_app

Wait until you observe the log entry confirming ready status:

INFO metabase.core :: Metabase Initialization COMPLETE

Nginx Reverse Proxy, TLS 1.3, and SSL Hardening

Never expose Metabase’s Jetty application port directly to the open Internet. Deploying Nginx as a reverse proxy provides TLS encryption, request rate limiting, connection caching, and HTTP/2 acceleration.

Step 1: Install Nginx and Certbot

sudo apt install -y nginx certbot python3-certbot-nginx

Step 2: Configure the Nginx Virtual Host

Create /etc/nginx/sites-available/metabase.conf (replace analytics.yourcompany.pk with your real DNS record):

server {
    listen 80;
    listen [::]:80;
    server_name analytics.yourcompany.pk;

    # Redirect all plain HTTP traffic to secure HTTPS
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name analytics.yourcompany.pk;

    # SSL Configuration (Certbot will manage certificate paths)
    ssl_certificate /etc/letsencrypt/live/analytics.yourcompany.pk/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/analytics.yourcompany.pk/privkey.pem;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
    ssl_prefer_server_ciphers off;
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 1d;

    # Security Headers
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-XSS-Protection "1; mode=block" always;
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;

    # Maximum payload size for exports and uploads
    client_max_body_size 64M;

    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_http_version 1.1;

        # WebSocket support for interactive dashboard polling
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";

        # Forwarded Client Identification
        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;

        # Extended timeouts for long-running analytical queries
        proxy_connect_timeout 300s;
        proxy_send_timeout 300s;
        proxy_read_timeout 300s;

        # Buffer settings
        proxy_buffering on;
        proxy_buffer_size 16k;
        proxy_buffers 8 32k;
        proxy_busy_buffers_size 64k;
    }
}

Step 3: Issue SSL Certificates and Reload Nginx

Ensure your DNS A record points to your Linux VPS public IP. Generate the Let’s Encrypt certificate:

# Enable site configuration
sudo ln -s /etc/nginx/sites-available/metabase.conf /etc/nginx/sites-enabled/
sudo nginx -t

# Obtain certificate via Certbot
sudo certbot --nginx -d analytics.yourcompany.pk --non-interactive --agree-tos --email [email protected]

# Test automated certificate renewal
sudo certbot renew --dry-run

# Reload Nginx to activate SSL
sudo systemctl reload nginx

Hardening Data Source Connections: Production Security Rules

Connecting your business intelligence tool to live customer databases requires rigorous security enforcement. If an analyst inadvertently runs an unindexed query or an attacker exploits credentials, your production databases must remain protected.

+---------------------------------------------------------------------------------------+
|                                Production Security Perimeter                           |
|                                                                                       |
|   +-----------------------+                    +----------------------------------+   |
|   |   Metabase VPS        |                    |   Production Database Server     |   |
|   |                       |   TLS Encrypted    |   (PostgreSQL / MySQL)           |   |
|   |  - Static Public IP   |===================>|                                  |   |
|   |  - SSH Bastion Tunnel |    Port 5432/3306  |  - Strict UFW Firewall Whitelist |   |
|   |  - Read-Only Role     |                    |  - Dedicated 'metabase_ro' User  |   |
|   +-----------------------+                    |  - Zero Write / Drop Privileges  |   |
|                                                +----------------------------------+   |
+---------------------------------------------------------------------------------------+

1. Enforcing Dedicated Read-Only Database Accounts

Never connect Metabase using root, postgres, or application master credentials. Create a strictly isolated read-only role with no write, update, or schema alteration permissions.

For PostgreSQL Data Sources:

Execute the following commands inside your production database:

-- 1. Create a dedicated read-only role with a strong password
CREATE ROLE metabase_ro WITH LOGIN PASSWORD 'YOUR_STRONG_ANALYTICS_PASSWORD';

-- 2. Grant connection rights to the target database
GRANT CONNECT ON DATABASE production_db TO metabase_ro;

-- 3. Connect to the production database and grant schema usage
\c production_db
GRANT USAGE ON SCHEMA public TO metabase_ro;

-- 4. Grant SELECT permissions on existing tables and views
GRANT SELECT ON ALL TABLES IN SCHEMA public TO metabase_ro;

-- 5. Guarantee that future tables automatically grant SELECT rights
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO metabase_ro;

-- 6. Prevent long-running rogue queries from exhausting DB worker threads
ALTER ROLE metabase_ro SET statement_timeout = '45000ms';

The statement_timeout directive acts as a safety brake: if any complex query runs for longer than 45 seconds, PostgreSQL automatically terminates the query process, preventing database thread starvation.

For MySQL / MariaDB Data Sources:

-- Create read-only user restricted to specific IP
CREATE USER 'metabase_ro'@'YOUR_METABASE_VPS_IP' IDENTIFIED BY 'YOUR_STRONG_ANALYTICS_PASSWORD';

-- Grant SELECT-only privileges
GRANT SELECT, SHOW VIEW ON production_db.* TO 'metabase_ro'@'YOUR_METABASE_VPS_IP';

-- Apply privilege tables
FLUSH PRIVILEGES;

2. Enforcing TLS/SSL Encrypted Data Transports

In the Metabase administrative interface under Admin Settings -> Databases -> Add Database:

  • Toggle Use an SSL-connection for database queries.
  • Select Verify CA or Require mode.
  • Paste your database cluster’s CA Root Certificate (ca.pem).

This ensures all query text and result sets traveling between your target database and the Metabase VPS are protected against man-in-the-middle inspection.

3. Network Perimeter Isolation

If your production database resides on another cloud host or local Pakistani datacenter, restrict incoming database ports (PostgreSQL 5432, MySQL 3306) at the firewall level exclusively to the static public IP of your Metabase Linux VPS:

# On the production database server: Allow traffic only from Metabase VPS IP
sudo ufw allow from 103.xxx.xxx.xxx to any port 5432 proto tcp comment "Metabase BI Static IP"

For maximum security, avoid exposing database ports publicly altogether. Configure Metabase’s native SSH Tunnel Bastion feature in the database connection settings to route all traffic securely over private keys.


Performance Tuning: Caching and Sync Scheduling

Without proper query caching and sync scheduling, business intelligence platforms can degrade the performance of underlying operational databases.

1. Configuring Query Result Caching

In high-traffic companies, multiple executives, branch managers, and team leads inspect identical dashboards at 9:00 AM every business day. Without caching, each dashboard refresh triggers dozens of redundant aggregate queries against your operational database.

Configure Metabase’s internal caching engine:

  1. Navigate to Admin Settings -> Performance.
  2. Enable Query Caching.
  3. Set the Minimum Query Duration for Caching to 2 seconds.
  4. Set the Default Cache TTL (Time-to-Live) to 60 minutes (or 360 minutes for historical financial reporting).
  5. Set the maximum cache storage footprint to 2048 MB.

When caching is activated, subsequent loads of executive dashboards are served directly from Metabase’s fast internal RAM in milliseconds, reducing production database CPU load to near zero.

2. Restricting Aggressive Database Sync & Fingerprinting

By default, Metabase executes periodic background scans to discover schema modifications and generate statistical column histograms (“fingerprints”). On tables containing tens of millions of rows, full table fingerprint scans cause severe disk I/O contention.

  1. Navigate to Admin Settings -> Databases -> [Your Database].
  2. Under Database Sync & Scanning:
    • Change Scan for Field Values from “Regularly, on a schedule” to “Only when filters are used” or “Disabled” on massive transactional ledger tables.
    • Schedule Database Schema Sync to run once daily during off-peak Pakistani business hours, such as 03:30 AM PKT.

Automated Reporting: Setting Up Slack and Corporate Email Alerts

One of Metabase’s most valuable features is its ability to push automated data digests and threshold alerts directly to corporate communication channels, keeping teams aligned without requiring them to log into the portal.

1. SMTP Email Server Configuration

To deliver scheduled dashboard summaries (known as “Subscriptions”) to management:

  1. Navigate to Admin Settings -> Email.
  2. Enter your corporate SMTP relay parameters:
    • SMTP Host: mail.yourdomain.pk (or transactional relay like SendGrid, Amazon SES, Postmark)
    • SMTP Port: 587
    • Security: STARTTLS
    • SMTP Username & Password: Secure authentication credentials
    • From Address: [email protected]
  3. Click Send Test Email to verify delivery.

2. Slack Webhook and Bot Integration

Metabase integrates natively with Slack to deliver automated daily snapshots and anomaly notifications:

  1. Create a Slack App in your company’s Slack workspace at api.slack.com/apps.
  2. Generate a Bot User OAuth Token with the following permissions:
    • chat:write
    • files:write
  3. In Metabase, navigate to Admin Settings -> Slack.
  4. Paste the Bot Token and select the default channel (e.g., #analytics-feed).
  5. Open any dashboard or individual question, click the Subscription button, and set a schedule (e.g., “Every weekday at 08:45 AM”). Select your target Slack channel (#leadership, #growth, or #sales-pk).

Metabase renders dashboard questions into clean, high-resolution PNG charts and posts them automatically alongside summary tables.


Automated Backup and Disaster Recovery Strategy

Because the Metabase application state lives inside the external PostgreSQL container, establishing a bulletproof backup pipeline requires backing up both the PostgreSQL database dump and uploaded dashboard assets.

Automated Nightly Backup Script

Create /opt/metabase/scripts/backup.sh:

#!/usr/bin/env bash
set -eo pipefail

BACKUP_DIR="/opt/metabase/backups"
TIMESTAMP=$(date +"%Y%m%d_%H%M%S")
RETENTION_DAYS=14

mkdir -p "${BACKUP_DIR}"

# Source environment variables
source /opt/metabase/.env

echo "[$(date)] Starting Metabase PostgreSQL metadata backup..."

# Execute pg_dump within the running database container
docker exec metabase_postgres_db pg_dump \
  -U "${POSTGRES_USER}" \
  -d "${POSTGRES_DB}" \
  -F c \
  -b \
  -v \
  -f "/tmp/metabase_backup_${TIMESTAMP}.dump"

# Copy backup file to host storage
docker cp "metabase_postgres_db:/tmp/metabase_backup_${TIMESTAMP}.dump" "${BACKUP_DIR}/metabase_backup_${TIMESTAMP}.dump"
docker exec metabase_postgres_db rm -f "/tmp/metabase_backup_${TIMESTAMP}.dump"

# Compress archive
gzip "${BACKUP_DIR}/metabase_backup_${TIMESTAMP}.dump"

echo "[$(date)] Backup complete: ${BACKUP_DIR}/metabase_backup_${TIMESTAMP}.dump.gz"

# Remove backups older than retention window
find "${BACKUP_DIR}" -type f -name "*.dump.gz" -mtime +${RETENTION_DAYS} -delete

# Optional: Push backup offsite using Rclone / S3 compatible storage
# rclone copy "${BACKUP_DIR}/metabase_backup_${TIMESTAMP}.dump.gz" remote:company-bi-backups/

Make the script executable and schedule it in crontab:

sudo chmod +x /opt/metabase/scripts/backup.sh

# Schedule nightly execution at 2:00 AM PKT
(crontab -l 2>/dev/null; echo "0 2 * * * /opt/metabase/scripts/backup.sh >> /var/log/metabase_backup.log 2>&1") | crontab -

When to Scale: Moving from VPS to Bare-Metal Dedicated Servers

A properly configured 4-core or 8-core Linux Cloud VPS comfortably serves mid-sized teams running queries against optimized, indexed analytical databases. However, as an organization scales its data infrastructure, certain operational thresholds indicate that virtualization boundaries are being reached.

Technical Bottlenecks Indicating Hardware Limits

  1. Hypervisor I/O Wait Throttling (wa%): When multiple analysts execute ad-hoc aggregate queries on unindexed multi-million-row operational tables, the server must read gigabytes of raw data blocks from disk. In virtualized multi-tenant environments, high random I/O wait (wa% > 20% in top) degrades query response times.
  2. CPU Steal Contention (st%): In shared public cloud VPS environments, CPU oversubscription by neighboring virtual machines leads to unpredictable CPU steal time (st%), causing visual dashboard rendering to stutter during peak morning hours.
  3. Massive In-Memory Data Warehouses: When your organization scales to multi-terabyte analytical databases (ClickHouse, PostgreSQL TimescaleDB, or distributed OLAP engines) running alongside Metabase on the same internal network, JVM heap requirements and database page caches expand beyond 32 GB or 64 GB of RAM.
  4. Strict Isolation and Regulatory Compliance: Financial institutions, digital banking pilots, and healthcare networks frequently require single-tenant, physically isolated compute infrastructure with zero shared memory or storage buses.

When your analytical queries outgrow shared virtualization, migrating your database engines and BI stack to unmetered, high-memory bare-metal Dedicated Servers eliminates hypervisor overhead entirely.

For organizations operating in Karachi, Lahore, Islamabad, and nationwide commercial centers, deploying on low-latency Dedicated Servers in Pakistan ensures that multi-gigabyte queries run over high-speed local enterprise networks with pure PCIe NVMe RAID throughput, dedicated multi-core Intel Xeon or AMD EPYC processors, and local data routing.


Engineering Implementation Checklist

Before opening your self-hosted Metabase deployment to executive leadership and department managers, verify all security and operational benchmarks:

  • Dedicated Linux VPS provisioned with pure NVMe storage and Ubuntu 24.04/22.04 LTS.
  • Docker Compose configured with an external PostgreSQL 16 container (default H2 disabled).
  • JVM memory flags explicitly configured (JAVA_OPTS="-Xms2g -Xmx4g") to prevent OOM termination.
  • Ports restricted: Port 3000 bound exclusively to 127.0.0.1, protected from direct external access.
  • Nginx configured with TLS 1.3, Let’s Encrypt automated SSL renewal, and WebSocket pass-through headers.
  • Read-only database roles (metabase_ro) provisioned on all target production databases with strict statement_timeout caps.
  • Database connections configured with TLS/SSL encryption or routed through private SSH bastion tunnels.
  • Query caching enabled with realistic TTL windows to eliminate redundant database load.
  • Database schema sync and deep field fingerprinting scheduled during off-peak hours (03:30 AM PKT).
  • Corporate SMTP relay verified and Slack automated subscriptions configured for key operational channels.
  • Automated nightly pg_dump backup script tested, verified, and scheduled via cron.
ENTERPRISE BI & ANALYTICS INFRASTRUCTURE

Deploy Metabase on Pure NVMe Linux VPS in Pakistan

Eliminate expensive per-seat USD SaaS licensing fees. Host your company's business intelligence dashboards on Nextgen's high-performance Linux VPS with dedicated IPv4, pure NVMe storage arrays, and 24/7 engineering support.