---
title: "Filtrer un dump PostgreSQL pour le rendre public"
description: "pg_dump ne sait pas filtrer des lignes. Voici comment je tire d'un seul dump PostgreSQL une version privée complète et une version publique par liste blanche."
date: "2026-06-25T09:00:00.000Z"
updated: "2026-10-29T13:21:04.462Z"
locale: "fr"
canonical: "https://ninhache.fr/fr/blog/tiers-donnees-public-prive"
author: "Néo Almeida"
tags: "architecture, data, etl"
categories: "dev"
---

# Filtrer un dump PostgreSQL pour le rendre public

pg_dump, l'outil de sauvegarde de PostgreSQL, exporte une base entière sans broncher. Le problème était ailleurs : cette même sauvegarde devait exister en deux versions, une que n'importe qui peut cloner et restaurer en local, une autre réservée à quelques personnes autorisées, sans dupliquer la logique d'extraction ni maintenir deux pipelines qui divergent avec le temps. Et pg_dump ne sait pas filtrer les lignes d'une table.

Le contexte concret : un ETL qui exporte périodiquement l'état d'un jeu live dans une base PostgreSQL (effets, sorts, objets, et d'autres tables dérivées d'un format binaire source). Une web-app restaure ensuite cette sauvegarde pour peupler sa propre base de développement. La question, simple en apparence : comment distribuer ces données à des développeurs externes qui contribuent au projet, sans exposer celles pas encore prêtes à être publiques ?

## Ce que pg_dump ne sait pas filtrer

pg_dump sait sauvegarder des objets : schémas, tables, séquences. Aucune de ses options n'accepte de clause `WHERE`. Celles qui s'en approchent filtrent toutes une table entière, jamais un enregistrement :

| Option | Ce qu'elle filtre | Pourquoi ça ne suffit pas |
|---|---|---|
| `--exclude-table-data` | toutes les données d'une table | tout ou rien : la table perd aussi ses données publiques |
| `pg_restore -L` (format archive) | les objets à restaurer | une table est restaurée en entier ou pas du tout |
| `--filter` (PostgreSQL 17) | des objets listés dans un fichier | toujours au niveau de la table ou du schéma |
| `COPY (SELECT ... WHERE ...)` | le résultat d'une requête | une requête par table, hors de la sauvegarde : la logique d'extraction est dupliquée |

Le besoin, lui, est au niveau de l'enregistrement : dans la table des objets, garder ceux qui sont prêts et retirer les autres. Le filtrage se fait donc sur la sauvegarde, après pg_dump, pas pendant.

## Un seul dump source, deux saveurs

La solution n'est pas deux pipelines séparés mais une seule extraction complète de la base, suivie d'une dérivation.

_La saveur privée est la sauvegarde complète. La saveur publique en est dérivée, jamais extraite à part._

Le point important, c'est le sens du filtrage. Quand une nouvelle catégorie d'objets apparaît dans la base (un nouveau type de cosmétique, un nouvel ensemble de contenus), elle n'atterrit pas automatiquement dans la saveur publique.

**Liste noire**

La liste dit "voici ce qu'il faut retirer". L'oubli laisse fuiter : un nouveau contenu non listé sort automatiquement.

**Liste blanche (retenue)**

La liste dit "voici ce qui est autorisé à sortir". Tout le reste est exclu : un nouveau contenu reste absent tant que quelqu'un ne l'a pas explicitement ajouté.

C'est la direction d'erreur qu'on veut quand l'enjeu est de ne pas publier des données non finalisées.

## Filtrer un dump PostgreSQL au format texte

Sans option de format, pg_dump écrit en sortie un fichier SQL (le format plain, celui par défaut) que psql sait rejouer dans une base vide. Les données de chaque table y arrivent en un bloc `COPY ... FROM stdin` : un enregistrement par ligne, puis `\.` pour fermer le bloc. C'est ce qui rend le filtre simple : un script parcourt le fichier et, dans les blocs des tables concernées, ne garde que les enregistrements dont l'identifiant est dans la liste blanche.

```sql title="dump.sql (extrait simplifié)"
COPY public.item (id, name) FROM stdin;
1201    Objet publié
1202    Objet pas encore annoncé -- [!code --]
1203    Objet publié
\.
```

Tout le reste traverse sans modification : le schéma, les séquences, les tables non sensibles. Le fichier filtré garde la forme de l'original et se restaure de la même façon : la commande de restauration n'a pas besoin de savoir qu'il a été filtré. Ce qui compte n'est pas ce script précis mais l'idée que le filtrage se fait en aval d'une seule sauvegarde source, jamais en dupliquant la requête d'extraction.

## Les releases GitHub comme CDN du pauvre

Reste à distribuer les deux fichiers. Pas besoin d'un serveur de fichiers, d'un CDN dédié ni d'un bucket de stockage objet : les releases GitHub font très bien l'affaire pour ce volume et cette fréquence de publication. Chaque release est taguée avec la version de la source de données, et porte un unique asset : la sauvegarde compressée.

La distinction entre les deux saveurs se joue sur deux axes : le dépôt cible et un suffixe de tag.

**Saveur publique**

Tag `data-v<version>`, dépôt principal `<org>/<projet-public>`, ouvert à tous.

**Saveur privée**

Tag `data-v<version>-private`, dépôt restreint `<org>/<projet-donnees-privees>`, marquée comme prerelease.

Le flag prerelease n'est pas cosmétique : il signale que cette release n'est pas destinée à une consommation générale, même pour quelqu'un qui verrait le dépôt par erreur ou par curiosité. C'est une deuxième barrière, indépendante des droits d'accès au dépôt lui-même.

## Le contrat côté consommateur

Côté web-app, la commande de seed qui télécharge ces sauvegardes et lance leur restauration dans une base locale ne connaît pas la notion de "confidentiel". Elle connaît une saveur et un dépôt, fournis par variables d'environnement, avec des valeurs par défaut qui pointent vers le cas public :

```bash title=".env (web-app)"
SEED_FLAVOR=public                 # ou "private"
SEED_REPO=<org>/<projet-public>    # ou le repo restreint
```

Résoudre la bonne release revient à interroger l'API des releases du dépôt ciblé et à filtrer par motif de tag (avec le suffixe pour le privé, sans pour le public). Un développeur externe qui ne fournit aucune variable obtient par construction la saveur publique du dépôt public. Un développeur autorisé sur le tier privé doit pointer explicitement vers l'autre dépôt et l'autre saveur, avec un jeton d'authentification qui n'existe que pour les personnes qui en ont besoin.

Ce contrat est propre parce qu'il ne repose sur aucune logique cachée côté client : toute la décision d'autorisation vit dans "qui a accès à quel dépôt et à quel jeton", pas dans le code de la commande de seed. Le code de résolution est identique pour les deux tiers, seule la configuration change.

## Pourquoi séparer plutôt que tout ouvrir

La tentation naturelle serait de tout publier et de laisser chacun filtrer sa base côté client. Ça ne marche pas ici parce que la raison d'exclusion n'est pas un problème de volume ou de pertinence, c'est un problème de maturité du contenu. Une base de données issue de fichiers de jeu bruts contient inévitablement des artefacts qui n'ont jamais été destinés à sortir : du contenu en cours de finalisation, des entrées de test laissées par les outils internes, des éléments retirés mais pas encore nettoyés de la source. Rien de tout ça n'a de valeur pour un contributeur externe.

**Warning**

Publier trop tôt l'un de ces éléments peut avoir des conséquences (annonce non désirée, contenu incomplet visible avant l'heure) qu'aucun filtrage côté client ne peut réparer après coup.

## Au-delà des données d'un jeu

Ce pattern dépasse largement le cas d'un jeu. Dès qu'un pipeline produit des données dont une partie doit rester à diffusion restreinte, la même structure s'applique, et le contrat entre producteur et consommateur se réduit à quelques variables d'environnement : quelle saveur, quel dépôt, quel suffixe de tag. Il reste testable et réutilisable, sans jamais coupler le code du consommateur à la notion de confidentialité.

**Le pattern en 4 points**

- Une seule extraction : la saveur privée est la sauvegarde complète, la saveur publique en est dérivée.
- pg_dump filtre des tables, pas des lignes : le tri par enregistrement se fait sur le fichier SQL, en aval, avant toute restauration.
- Une liste blanche plutôt qu'une liste noire : un contenu oublié reste privé au lieu de fuiter.
- Des releases versionnées, avec un flag prerelease sur le tier privé, servent de canal et d'historique en même temps.

[Reseed de données sans downtime via une commande maison](/fr/blog/reseed-sans-downtime)
