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
- Allez dans Admin Settings → Databases → Add database
- Remplissez :
- Database type : PostgreSQL
- Display name : Analytics Warehouse
- Host : postgres
- Port : 5432
- Database name : analytics
- Username : metabase
- Password : (votre PG_PASSWORD)
- 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 byCountryetPlan - 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
- PG_PASSWORD — utilisez un fichier
.envhors versionning (ajoutez-le au.gitignore) - Metabase HTTPS — placez un reverse proxy Nginx ou Caddy devant :
analytics.mondomaine.fr { reverse_proxy localhost:3000 } - Restreignez l’accès PostgreSQL — dans
docker-compose.yml, retirez le port mapping5432:5432si vous n’avez pas besoin d’accès externe - Sauvegardes — planifiez un
pg_dumpquotidien :#!/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
- Branchez Airbyte pour ingérer automatiquement vos sources (Stripe, Google Analytics, HubSpot, base de production)
- Ajoutez dbt pour modéliser vos transformations SQL avec tests et documentation
- Migrez vers Metabase Enterprise si vous avez besoin de SSO, d’embedding ou de row-level permissions
- Activez les alertes Metabase pour être notifié quand un KPI franchit un seuil critique
- 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
- Metabase Documentation — metabase.com
- Metabase GitHub Repository — github.com
- PostgreSQL 16 Documentation — postgresql.org
- PostgreSQL Docker Image — hub.docker.com
- Metabase Learn : tutoriels et bonnes pratiques — metabase.com
- SaaS Benchmarks Report June 2026 — saastr.com
📖 À lire aussi : Pipeline ETL Python avec Polars et PostgreSQL : le guide complet en 30 minutes
Commentaires (0)
Laisser un commentaire
Les commentaires sont modérés. Questions WordPress, cybersécurité ou dev web bienvenues.