En 2026, 67 % des PME paient un abonnement BI qu’elles sous-utilisent à moins de 30 % de ses capacités, selon une étude SaaS Benchmarks de juin 2026. Pendant ce temps, Metabase — l’alternative open-source qui cumule 55 000 étoiles GitHub — propose l’essentiel de la business intelligence sans la complexité ni la facture salée de Tableau, Looker ou Power BI. Voici comment déployer une stack analytics complète, prête pour la production, en 30 minutes chrono.

Pourquoi Metabase + PostgreSQL ?

Ce duo a trois atouts majeurs :

  • Zéro licence — les deux sont open-source, y compris en production
  • Interface en langage naturel — Metabase intègre un query builder visuel et un mode « Ask a question » qui génère du SQL automatiquement depuis une phrase en français
  • PostgreSQL comme couteau suisse — JSONB, window functions, index partiels, CTE, vues matérialisées : vous avez toute la puissance nécessaire pour un data warehouse de 10 Go à 10 To

Étape 1 : le docker-compose qui fait tout

Créez un fichier docker-compose.yml à la racine de votre projet :

version: '3.8'

services:
  postgres:
    image: postgres:16-alpine
    container_name: analytics_pg
    environment:
      POSTGRES_USER: metabase
      POSTGRES_PASSWORD: ${PG_PASSWORD:-changeMe2026!}
      POSTGRES_DB: analytics
    ports:
      - "5432:5432"
    volumes:
      - pg_data:/var/lib/postgresql/data
      - ./init-scripts:/docker-entrypoint-initdb.d
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U metabase -d analytics"]
      interval: 10s
      timeout: 5s
      retries: 5
    command: |
      -c shared_buffers=512MB
      -c effective_cache_size=2GB
      -c work_mem=64MB
      -c random_page_cost=1.1

  metabase:
    image: metabase/metabase:v0.52.4
    container_name: analytics_mb
    ports:
      - "3000:3000"
    environment:
      MB_DB_TYPE: postgres
      MB_DB_DBNAME: metabase_app
      MB_DB_PORT: 5432
      MB_DB_USER: metabase
      MB_DB_PASS: ${PG_PASSWORD:-changeMe2026!}
      MB_DB_HOST: postgres
      MB_SITE_URL: http://localhost:3000
    volumes:
      - mb_data:/metabase-data
    depends_on:
      postgres:
        condition: service_healthy

volumes:
  pg_data:
  mb_data:
# Créer le fichier .env avec un mot de passe robuste
echo "PG_PASSWORD=$(openssl rand -base64 24)" > .env

# Lancer la stack
docker compose up -d

# Vérifier que tout tourne
docker compose ps
# Doit afficher analytics_pg et analytics_mb en état "Up"

À ce stade, Metabase est accessible sur http://localhost:3000. Le setup wizard vous demandera de créer un compte admin — faites-le, on se retrouve juste après.

Étape 2 : préparer la base de données analytics

Créez le script init-scripts/01-schema.sql — il sera exécuté automatiquement au premier démarrage de PostgreSQL :

-- Structure analytics pour une marketplace e-commerce

-- Table des utilisateurs
CREATE TABLE IF NOT EXISTS users (
    user_id       BIGSERIAL PRIMARY KEY,
    email         TEXT NOT NULL UNIQUE,
    full_name     TEXT NOT NULL,
    country       TEXT,
    plan          TEXT CHECK (plan IN ('free', 'pro', 'enterprise')),
    created_at    TIMESTAMPTZ DEFAULT NOW(),
    last_login_at TIMESTAMPTZ
);

-- Table des commandes
CREATE TABLE IF NOT EXISTS orders (
    order_id      BIGSERIAL PRIMARY KEY,
    user_id       BIGINT REFERENCES users(user_id),
    status        TEXT CHECK (status IN ('pending', 'paid', 'shipped', 'cancelled')),
    amount_eur    NUMERIC(10,2) NOT NULL CHECK (amount_eur > 0),
    currency      TEXT DEFAULT 'EUR',
    created_at    TIMESTAMPTZ DEFAULT NOW(),
    shipped_at    TIMESTAMPTZ
);

-- Index pour les requêtes analytics fréquentes
CREATE INDEX idx_orders_user_id   ON orders(user_id);
CREATE INDEX idx_orders_created   ON orders(created_at DESC);
CREATE INDEX idx_orders_status    ON orders(status) WHERE status IN ('paid', 'shipped');

-- Vue matérialisée : chiffre d'affaires quotidien
CREATE MATERIALIZED VIEW mv_daily_revenue AS
SELECT
    DATE(created_at)  AS day,
    COUNT(*)          AS total_orders,
    SUM(amount_eur)   AS revenue_eur,
    AVG(amount_eur)   AS avg_order_value,
    COUNT(DISTINCT user_id) AS unique_customers
FROM orders
WHERE status IN ('paid', 'shipped')
GROUP BY DATE(created_at)
ORDER BY day DESC;

CREATE UNIQUE INDEX idx_mv_daily_revenue_day ON mv_daily_revenue(day);

Ajoutez un script de données de test pour avoir de la matière à explorer :

-- init-scripts/02-seed.sql
INSERT INTO users (email, full_name, country, plan, created_at)
SELECT
    'user_' || g || '@example.com',
    'User ' || g,
    (ARRAY['FR','DE','UK','ES','IT'])[1 + floor(random()*5)],
    (ARRAY['free','free','pro','pro','enterprise'])[1 + floor(random()*5)],
    NOW() - (random() * INTERVAL '365 days')
FROM generate_series(1, 500) g;

INSERT INTO orders (user_id, status, amount_eur, created_at, shipped_at)
SELECT
    1 + floor(random() * 500),
    (ARRAY['paid','paid','paid','paid','shipped','shipped','cancelled'])[1 + floor(random()*7)],
    round((20 + random() * 480)::numeric, 2),
    NOW() - (random() * INTERVAL '90 days'),
    CASE WHEN random() > 0.3 THEN NOW() - (random() * INTERVAL '85 days') END
FROM generate_series(1, 5000) g
WHERE EXISTS (SELECT 1 FROM users WHERE user_id = 1 + floor(random() * 500));

-- Rafraîchir la vue matérialisée
REFRESH MATERIALIZED VIEW mv_daily_revenue;

Relancez PostgreSQL pour charger les seeds : docker compose down -v && docker compose up -d

Étape 3 : connecter Metabase à vos données

  1. Allez dans Admin Settings → Databases → Add database
  2. Remplissez :
    • Database type : PostgreSQL
    • Display name : Analytics Warehouse
    • Host : postgres
    • Port : 5432
    • Database name : analytics
    • Username : metabase
    • Password : (votre PG_PASSWORD)
  3. Cliquez Save — Metabase scanne automatiquement les tables et vues

Étape 4 : créer 3 dashboards en 15 minutes

Dashboard 1 : Revenue Tracker

Allez dans New → Question, sélectionnez la table mv_daily_revenue, choisissez le type de visualisation Area Chart :

  • Axe X : Day (filtrer sur les 30 derniers jours)
  • Axe Y : Revenue EUR (somme)
  • Sauvegardez la question sous « Revenue 30j »

Ajoutez une KPI Card :

-- Question SQL native pour la KPI de revenue total
SELECT
    SUM(amount_eur) AS "Chiffre d'affaires total",
    COUNT(*) AS "Commandes"
FROM orders
WHERE status IN ('paid', 'shipped');

Dashboard 2 : Analyse utilisateurs par pays et plan

Créez une question avec le Query Builder sur la table users :

  • Summarize : Count → Group by Country et Plan
  • Visualization : Stacked Bar Chart

Pour un tableau détaillé, passez en SQL natif :

-- Distribution des utilisateurs avec CA moyen
SELECT
    u.country,
    u.plan,
    COUNT(DISTINCT u.user_id)               AS nb_users,
    COUNT(DISTINCT o.order_id)               AS nb_orders,
    ROUND(AVG(o.amount_eur)::numeric, 2)     AS panier_moyen_eur,
    ROUND(SUM(o.amount_eur)::numeric, 2)     AS ca_total_eur
FROM users u
LEFT JOIN orders o ON u.user_id = o.user_id AND o.status IN ('paid', 'shipped')
GROUP BY u.country, u.plan
ORDER BY ca_total_eur DESC NULLS LAST;

Dashboard 3 : Conversion funnel

Visualisez le funnel de conversion des commandes :

-- Funnel de statuts de commande
SELECT
    status,
    COUNT(*)   AS nombre,
    ROUND(COUNT(*) * 100.0 / SUM(COUNT(*)) OVER(), 1) AS pourcentage
FROM orders
WHERE created_at > NOW() - INTERVAL '90 days'
GROUP BY status
ORDER BY
    CASE status
        WHEN 'pending'   THEN 1
        WHEN 'paid'      THEN 2
        WHEN 'shipped'   THEN 3
        WHEN 'cancelled' THEN 4
    END;

Sélectionnez la visualisation Funnel dans Metabase.

Étape 5 : automatiser et sécuriser

Rafraîchissement automatique des vues matérialisées

Ajoutez un cronjob qui rafraîchit mv_daily_revenue toutes les heures :

# crontab -e (sur le serveur hôte)
0 * * * * docker exec analytics_pg psql -U metabase -d analytics 
  -c "REFRESH MATERIALIZED VIEW CONCURRENTLY mv_daily_revenue;"

Pour un environnement plus robuste, utilisez le scheduler intégré de Metabase via une pulsar card ou un appel API :

# Déclencher le refresh via l'API Metabase
curl -X POST "http://localhost:3000/api/database/2/sync_schema" 
  -H "X-Metabase-Session: ${MB_SESSION_TOKEN}"

Sécurisation minimale avant la production

  1. PG_PASSWORD — utilisez un fichier .env hors versionning (ajoutez-le au .gitignore)
  2. Metabase HTTPS — placez un reverse proxy Nginx ou Caddy devant :
    analytics.mondomaine.fr {
        reverse_proxy localhost:3000
    }
  3. Restreignez l’accès PostgreSQL — dans docker-compose.yml, retirez le port mapping 5432:5432 si vous n’avez pas besoin d’accès externe
  4. Sauvegardes — planifiez un pg_dump quotidien :
    #!/bin/bash
    # backup.sh — à placer dans un cron quotidien
    docker exec analytics_pg pg_dump -U metabase analytics 
      | gzip > "/backups/analytics_$(date +%Y%m%d).sql.gz"
    # Garder les 30 derniers jours
    find /backups/ -name 'analytics_*.sql.gz' -mtime +30 -delete

Ce que vous avez construit

Composant Rôle Alternative payante
PostgreSQL 16 Data warehouse OLAP Snowflake ($$$$), BigQuery ($$$)
Metabase v0.52 BI / Visualisation / Dashboards Tableau ($$$$), Looker ($$$$), Power BI ($$)
Docker Compose Orchestration / Déploiement Kubernetes, ECS
Caddy / Nginx Reverse proxy HTTPS CloudFront, Cloudflare

Coût total en auto-hébergement sur un VPS 4 Go RAM : ~12 € / mois. L’équivalent cloud managé (Snowflake + Tableau) : ~1 500 € / mois minimum. La différence finance un poste de développeur.

Et après ? Les extensions naturelles

  1. Branchez Airbyte pour ingérer automatiquement vos sources (Stripe, Google Analytics, HubSpot, base de production)
  2. Ajoutez dbt pour modéliser vos transformations SQL avec tests et documentation
  3. Migrez vers Metabase Enterprise si vous avez besoin de SSO, d’embedding ou de row-level permissions
  4. Activez les alertes Metabase pour être notifié quand un KPI franchit un seuil critique
  5. Passez à l’échelle avec TimescaleDB (extension PostgreSQL) si vos données deviennent volumineuses et temporelles

En 30 minutes, vous avez une stack analytics fonctionnelle, performante et extensible. Le plus dur n’est pas la technique — c’est de définir les bonnes questions métier auxquelles vos dashboards doivent répondre. Commencez par trois KPIs, itérez, et résistez à la tentation du dashboard à 40 widgets que personne ne regarde.


Sources et références

📖 À lire aussi : Pipeline ETL Python avec Polars et PostgreSQL : le guide complet en 30 minutes

W
WP Admin Lab

Architecte web full-stack. WordPress, performance, data et sécurité. Notes de terrain, tests reproductibles et retours d'expérience.