Self-Hosting Guide: Deploy Metabase for Business Intelligence
Self-Hosting Guide: Deploy Metabase for Business Intelligence
Metabase is the most popular open source BI tool — it lets non-technical users explore data, build dashboards, and share insights without writing SQL. Self-hosting gives you unlimited users and dashboards for free.
Requirements
- VPS with 2 GB RAM minimum (4 GB recommended)
- Docker and Docker Compose
- Domain name (e.g.,
bi.yourdomain.com) - 10+ GB disk
- A database to analyze (PostgreSQL, MySQL, etc.)
Step 1: Create Docker Compose
# docker-compose.yml
services:
metabase:
image: metabase/metabase:latest
container_name: metabase
restart: unless-stopped
ports:
- "3000:3000"
environment:
- MB_DB_TYPE=postgres
- MB_DB_DBNAME=metabase
- MB_DB_PORT=5432
- MB_DB_USER=metabase
- MB_DB_PASS=your-strong-password
- MB_DB_HOST=db
- MB_SITE_URL=https://bi.yourdomain.com
- MB_ENCRYPTION_SECRET_KEY=your-random-32-char-key
depends_on:
- db
db:
image: postgres:16-alpine
container_name: metabase-db
restart: unless-stopped
volumes:
- postgres_data:/var/lib/postgresql/data
environment:
- POSTGRES_DB=metabase
- POSTGRES_USER=metabase
- POSTGRES_PASSWORD=your-strong-password
volumes:
postgres_data:
Note: This PostgreSQL is for Metabase's own data (dashboards, users, settings). Your business data lives in separate databases that you'll connect in Step 6.
Step 2: Generate Encryption Key
openssl rand -hex 16
This encrypts database credentials stored in Metabase.
Step 3: Start Metabase
docker compose up -d
First boot takes 1-2 minutes to initialize.
Step 4: Reverse Proxy (Caddy)
# /etc/caddy/Caddyfile
bi.yourdomain.com {
reverse_proxy localhost:3000
}
sudo systemctl restart caddy
Step 5: Initial Setup Wizard
- Open
https://bi.yourdomain.com - Select your language
- Create admin account
- Connect your first database (can skip and add later)
- Choose usage tracking preference
Step 6: Connect Your Data Sources
Go to Admin → Databases → Add database:
| Database | Connection String |
|---|---|
| PostgreSQL | host:5432/dbname |
| MySQL | host:3306/dbname |
| MongoDB | mongodb://host:27017/dbname |
| SQLite | /path/to/database.db |
| BigQuery | Service account JSON |
| Snowflake | Account + credentials |
| Redshift | host:5439/dbname |
Tip: Create a read-only database user for Metabase:
CREATE USER metabase_reader WITH PASSWORD 'read-only-password';
GRANT CONNECT ON DATABASE myapp TO metabase_reader;
GRANT USAGE ON SCHEMA public TO metabase_reader;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO metabase_reader;
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO metabase_reader;
Step 7: Build Your First Dashboard
Create a question (query):
- Click New → Question
- Pick your database and table
- Use the visual query builder or write SQL
- Save the question
Build a dashboard:
- Click New → Dashboard
- Add saved questions as cards
- Add filters (date range, category, etc.)
- Arrange and resize cards
- Save and share
Common dashboard patterns:
| Dashboard | Metrics |
|---|---|
| Revenue | MRR, churn rate, LTV, new subscriptions |
| Product | DAU/MAU, feature usage, retention cohorts |
| Support | Ticket volume, response time, CSAT scores |
| Marketing | Traffic, conversion rate, CAC, channel performance |
Step 8: Configure SMTP for Alerts
Admin → Settings → Email:
| Setting | Value |
|---|---|
| SMTP Host | smtp.resend.com |
| SMTP Port | 587 |
| SMTP Security | TLS |
| SMTP Username | resend |
| SMTP Password | re_your_api_key |
| From Address | bi@yourdomain.com |
Enables:
- Scheduled dashboard emails (daily/weekly reports)
- Alert notifications (when metrics cross thresholds)
- User invitation emails
Step 9: Set Up Alerts
- Open a question/chart
- Click the bell icon → Create alert
- Choose condition: "When results go above/below X"
- Set recipients and frequency
- Alerts fire automatically when conditions are met
Step 10: Embedding (Optional)
Embed Metabase dashboards in your app:
// Generate signed embed URL (server-side)
const jwt = require('jsonwebtoken');
const METABASE_SITE_URL = 'https://bi.yourdomain.com';
const METABASE_SECRET_KEY = 'your-embedding-secret-key';
const payload = {
resource: { dashboard: 1 },
params: {},
exp: Math.round(Date.now() / 1000) + (10 * 60), // 10 min expiration
};
const token = jwt.sign(payload, METABASE_SECRET_KEY);
const embedUrl = `${METABASE_SITE_URL}/embed/dashboard/${token}`;
<iframe
src="https://bi.yourdomain.com/embed/dashboard/TOKEN"
width="100%"
height="600"
frameborder="0"
></iframe>
Enable embedding in Admin → Settings → Embedding.
Production Hardening
Environment tuning:
environment:
- JAVA_OPTS=-Xmx2g # Increase for large datasets
- MB_JETTY_MAXTHREADS=100
- MB_ASYNC_QUERY_THREAD_POOL_SIZE=10
Backups:
# Database backup (daily cron)
docker exec metabase-db pg_dump -U metabase metabase > /backups/metabase-$(date +%Y%m%d).sql
Updates:
docker compose pull
docker compose up -d
Security:
- Use read-only database users
- Enable SSL for all database connections
- Restrict Admin access to specific users
- Set session timeout in Admin settings
Resource Usage
| Users | RAM | CPU | Disk |
|---|---|---|---|
| 1-10 | 2 GB | 2 cores | 10 GB |
| 10-50 | 4 GB | 4 cores | 15 GB |
| 50-200 | 8 GB | 8 cores | 30 GB |
VPS Recommendations
| Provider | Spec (20 users) | Price |
|---|---|---|
| Hetzner | 4 vCPU, 8 GB RAM | €8/month |
| DigitalOcean | 2 vCPU, 4 GB RAM | $24/month |
| Linode | 2 vCPU, 4 GB RAM | $24/month |
Compare BI tools on OSSAlt — features, data sources, and self-hosting options side by side.