BesluitBron fonctionne en tant que service public sur besluitbron.nl. Si vous souhaitez exploiter ce service au sein de votre propre organisation, par exemple pour maintenir le trafic au sein de votre propre réseau, vous pouvez le faire à l'aide de la même image de conteneur.
La configuration en production
flowchart TD
NET["Internet"] --> CAD["Caddy<br/>Terminaison TLS<br/>pour BesluitBron.nl"]
CAD --> APP["BesluitBron<br/>conteneur, un port"]
APP --> VOL[("Dossier de données<br/>journal, exportations,<br/>certificat")]
APP --> PG[("PostgreSQL 18<br/>obligatoire")]
APP --> UP["Plateformes de sources<br/>HTTPS sortant"]
- Caddy se charge de la terminaison TLS et achemine le trafic vers le conteneur. Il s'agit simplement de protocole HTTP au sein du réseau partagé ;
- Le dossier de données contient le journal des requêtes, les exportations et, en cas d'utilisation d'un TLS propre, le certificat. C'est ce dossier qui doit être inclus dans une sauvegarde et dans une politique de nettoyage ;
- PostgreSQL est requis. Les paramètres et le fichier journal se trouvent dans PostgreSQL 18. Jusqu'à la version 1.3.50 incluse, il s'agissait de PostgreSQL 17. Jusqu'à la version 1.3.72 incluse, la base de données était facultative et le service écrivait dans des fichiers sans base de données ; à partir de la version 1.3.73, une installation sans base de données ne démarre pas.
Mise à jour
L'image est fournie par version pour amd64 et arm64, via un registre externe à ce site. L'adresse figure sur la page publique « Exécuter soi-même » dès qu'un administrateur l'a renseignée, voir Installatie. Une nouvelle version est déployée en remplaçant l'image et en redémarrant le conteneur. Le registre des connecteurs est calculé une seule fois au démarrage ; un redémarrage est donc également nécessaire pour appliquer une modification du circuit source. Voir Connectorregister.
Deux conteneurs peuvent fonctionner simultanément pendant la migration. À partir de la version 1.3.51, un seul des deux travaille à la fois sur le modèle de données : le second attend que le premier ait terminé, puis poursuit l'opération. Le journal indique « Another instance is migrating this database; waiting for it. », suivi de « The other instance finished ». Un démarrage plus long que d’habitude s’explique donc ainsi et ne constitue pas un dysfonctionnement. Jusqu’à la version 1.3.50 incluse, deux conteneurs pouvaient interférer l’un avec l’autre, ce qui entraînait un message d’erreur indiquant « pg_class_relname_nsp_index ».
Le modèle de données effectue une auto-vérification à chaque démarrage
La base de données possède un numéro de version. Ce numéro indique quelles tables et colonnes doivent être présentes. À partir de la version 1.3.59, le service ne se contente plus de croire ce numéro sur parole. À chaque démarrage, le service compare la base de données avec ce que le modèle de données correspondant à ce numéro prévoit.
Si un tableau ou une colonne manque, le service le crée. Cela se fait avant l'application d'une version plus récente du modèle de données. Le journal du conteneur contient alors deux lignes, avec les codes bbnmig016 et bbnmig017. Elles indiquent toutes deux le nombre et le nom de chaque table et colonne. Ces lignes constituent un avertissement et non une panne : la réparation a déjà été effectuée.
Le service se contente d'ajouter des éléments. Une colonne déjà présente reste telle quelle, même si le modèle de données spécifie un autre type. Une colonne ou une table que le modèle de données ne reconnaît pas est conservée. Ainsi, une réparation ne peut pas entraîner la perte de données.
PostgreSQL ne peut pas ajouter à une table contenant des lignes une colonne dont le remplissage est obligatoire et qui ne possède pas de valeur par défaut. La mise à jour s'interrompt alors et génère un message d'erreur mentionnant la table et la colonne concernées. C'est alors à l'administrateur de décider quelle valeur attribuer aux lignes existantes. Cette opération s'effectue en dehors du service, à l'aide de SQL.
Jusqu'à la version 1.3.58 incluse, aucune vérification n'était effectuée. Une base de données pouvait ne pas contenir une colonne qui, d'après le numéro de version, était censée exister, sans qu'aucun message ne le signale. Le 18 août 2026, l'installation publique s'est bloquée à cause de cela. La mise à jour s'est arrêtée à la version 17 du modèle de données, avec un message concernant la colonne « template_key » dans la table « bbn_questions ». Le tableau de bord a ensuite affiché un message d'erreur concernant la colonne « organisation », qui aurait dû être ajoutée par la version 19.
Mise à jour de la base de données vers PostgreSQL 18
À partir de la version 1.3.51, la composition fournie fonctionne sous PostgreSQL 18. Une installation existante sous PostgreSQL 17 ne sera pas automatiquement prise en charge : PostgreSQL ne peut pas lire les données d'une version majeure antérieure. Il s'agit d'une caractéristique propre à PostgreSQL et non à ce service.
Le conteneur refuse donc de démarrer et s'arrête en affichant un message d'erreur. Celui-ci commence par « in 18+, these Docker images are configured to store database data in a format which is compatible with pg_ctlcluster ». C'est le scénario le plus sûr, et il a été vérifié : aucune base de données vide n'est créée à côté de l'ancienne. En effet, une base de données vide démarrerait normalement, après quoi le service recréerait ses tables et l'installation semblerait intacte alors que le journal aurait disparu.
Il y a encore un autre changement. Le volume se trouve désormais à l'adresse /var/lib/postgresql et non plus à /var/lib/postgresql/data. Les images de la version 18 et des versions ultérieures placent la base de données dans un dossier dédié à chaque version majeure, en l'occurrence /var/lib/postgresql/18/docker. Cela permet à la version majeure suivante de mettre à jour les données à leur emplacement d'origine.
Ceux qui fonctionnaient jusqu’à présent sans base de données ne perdront pas leur journal : les fichiers journaliers existants seront importés une seule fois lors du premier démarrage avec une base de données, puis déplacés vers un sous-dossier.
Mise à jour étape par étape
Les commandes ci-dessous se basent sur le fichier docker-compose.production.yml fourni, avec la base de données bbn, le propriétaire bbn_install et le volume bbn-pgdata. Si l'installation utilise d'autres noms, ceux-ci sont indiqués dans le fichier .env.
Vérifiez d'abord quelle version est actuellement installée. Si c'est la version 18, il n'y a rien à faire.
docker compose -f docker-compose.production.yml exec postgres psql -U bbn_install -d bbn -tAc "select version();"
Notez ensuite le nombre de lignes que comptent les plus grands tableaux. Ce chiffre servira plus tard à vérifier que tout a bien été transféré.
docker compose -f docker-compose.production.yml exec postgres psql -U bbn_install -d bbn \
-c "select count(*) from bbn_mcp_log;" -c "select count(*) from bbn_errors;"
Ensuite, arrêtez uniquement l'application. La base de données continuera de fonctionner pendant un certain temps, car c'est de là que doit provenir le dump.
docker compose -f docker-compose.production.yml stop bbn_web
Créez la sauvegarde à l'aide de l'pg_dump, qui se trouve dans le conteneur en cours d'exécution. Il s'agit de la version correspondant à l'ancienne base de données, et c'est exactement le but recherché. Le fichier sera transféré sur l'hôte.
docker compose -f docker-compose.production.yml exec -T postgres \
pg_dump -U bbn_install -d bbn -Fc > bbn-17.dump
ls -l bbn-17.dump
Les rôles bbn_install et bbn_web sont associés au serveur et non à la base de données ; ils ne figurent donc pas dans la sauvegarde. Il n'est pas non plus nécessaire de les conserver séparément : bbn_install est créé par l'image à partir de POSTGRES_USER, et bbn_web crée lui-même le service lors du premier démarrage.
Arrêtez maintenant tout et enregistrez l'ancien volume sous un autre nom. Le nouveau volume doit être vide, sinon PostgreSQL 18 ne démarrera pas.
docker compose -f docker-compose.production.yml down
docker volume create bbn-pgdata-17
docker run --rm -v mcp2ori_bbn-pgdata:/from -v bbn-pgdata-17:/to alpine sh -c "cp -a /from/. /to/"
docker volume rm mcp2ori_bbn-pgdata
Dans Docker, le volume s'appelle « <project>_bbn-pgdata ». Le nom du projet correspond au dossier contenant le fichier compose, ici « mcp2ori ». La commande « docker volume ls » affiche le nom réel.
Ce nom ne comporte pas de numéro de version, et c'est un choix délibéré. Un nom qui évoluerait avec l'image pointerait, lors d'une prochaine version majeure, vers un volume qui n'existe pas encore. Docker créerait alors simplement ce volume, vide. PostgreSQL y place une nouvelle base de données, le service recrée ses tables et l'installation semble fonctionner correctement alors que le journal a disparu. Avec un nom fixe, cela ne peut pas se produire : les anciennes données sont toujours présentes, et le conteneur refuse de démarrer. Ce refus constitue la protection.
Le volume conservé se voit toutefois attribuer un numéro de version, ici bbn-pgdata-17. Ce volume n'est plus utilisé par aucune image ; son nom est donc le seul élément permettant de savoir ce qu'il contient.
La version contenue dans le volume actif est toujours visible. Le dossier porte le nom de la version principale.
docker run --rm -v mcp2ori_bbn-pgdata:/v alpine ls /v
Lancez ensuite uniquement la base de données. Docker recrée le volume, désormais vide, et l'image y insère une base de données de 18.
docker compose -f docker-compose.production.yml up -d postgres
docker compose -f docker-compose.production.yml exec postgres psql -U bbn_install -d bbn -tAc "select version();"
Créez le rôle d'exécution avant de restaurer le dump. Le dump contient des droits pour « bbn_web », et sans ce rôle, pg_restore signale une erreur à ce sujet. Le mot de passe est « BBN_WEB_PASSWORD », issu de .env.
docker compose -f docker-compose.production.yml exec postgres \
psql -U bbn_install -d bbn -c "create role bbn_web login password 'HET_WACHTWOORD_UIT_ENV';"
Dans ce cas, relisez le dump.
docker compose -f docker-compose.production.yml exec -T postgres \
pg_restore -U bbn_install -d bbn < bbn-17.dump
Les notifications concernant les droits pour bbn_web n'ont pas d'importance ici. Le service redéfinit ces droits à chaque démarrage, pour chaque table existante à ce moment-là.
Enfin, démarrez le service.
docker compose -f docker-compose.production.yml up -d
Vérifier après la mise à jour
Vérifiez ces quatre éléments, dans cet ordre. Si l'un d'entre eux ne correspond pas, c'est que l'ancien volume est toujours présent.
- La base de données fonctionne sous la version 18. Répétez la commande avec «
select version();». - Le nombre de lignes correspond à celui indiqué avant le vidage.
- Le service a démarré et a vérifié le modèle de données. Le journal du conteneur indique « Data model is up to date » accompagné du numéro de version, ou « Upgrading data model » lorsque l'image est plus récente que la base de données. Ces lignes peuvent être consultées à l'adresse
docker compose -f docker-compose.production.yml logs bbn_web | grep bbnmig. - L'écran « Données » de la console d'administration affiche tous les tableaux contenant les chiffres prévisionnels, et
/healthrépertorie tous les connecteurs.
Si tout est correct, le volume sauvegardé peut être supprimé.
docker volume rm bbn-pgdata-17
Si quelque chose ne fonctionne pas comme prévu, il est possible de revenir en arrière : arrêter le service, supprimer le nouveau volume, restaurer le volume sauvegardé sous son nom d'origine et relancer l'ancienne image.
Ce qu'un administrateur doit mettre en place
- Configurer le mot de passe administrateur. À partir de la version 1.3.21, l'image ne fournit plus de mot de passe par défaut et le service refuse de démarrer si aucun mot de passe n'est défini. Le fichier de composition de production récupère le mot de passe depuis
BBN_ADMIN_PASSWORDdans le répertoire.envet s'arrête si celui-ci est manquant. Jusqu'à la version 1.3.21, le service démarrait avec un mot de passe par défaut public, ce qui déclenchait un avertissement dans la console ; - Déterminer si les adresses MCP peuvent être accessibles au public. Elles ne font l'objet d'aucune authentification, ce qui constitue un choix délibéré pour les données publiques. Au sein d'une organisation, une restriction d'accès au réseau peut toutefois s'avérer souhaitable. Voir Openbaarheid en Persoonsgegevens ;
- Pour choisir une durée de conservation pour le dossier de données, voir Logging en Verantwoording ;
- Choisir les ressources proposées. Moins il y a de ressources, moins il y a d’outils qui se disputent l’attention de l’assistant ; voir Connector ;
- Il est important de savoir ce que dit
sitemap.xmlà propos des dates, car un administrateur reçoit des questions à ce sujet. À partir de la version 1.3.35, chaque page de ce fichier comporte une date de modification (lastmod). Pour une page de documentation, il s'agit du jour où le texte lui-même a été modifié, tel qu'indiqué dans l'historique des versions. Pour toute autre page, il s'agit de la date à laquelle cette version a été générée, car ce texte est contenu dans l'image : il n'a donc pas pu être modifié avant. La conséquence est clairement indiquée : une nouvelle version réinitialise cette date sur toutes les pages, y compris celles qui n'ont pas été modifiées. Jusqu’à la version 1.3.34 incluse, seules les pages de documentation comportaient une date, contrairement aux autres. Si la date de compilation ne peut être lue, la date n’apparaît pas plutôt que d’être inventée ; - Nommer l'environnement. À compter du 1er février 2024, la visibilité pour les moteurs de recherche dépendra du libellé utilisé sous
BesluitBron:Environment:Name. Une installation portant le nom « Production » sera indexée ; tout autre libellé sera ignoré par les moteurs de recherche, tant surrobots.txtque dans l'en-tête de chaque réponse et sur la page elle-même. Un environnement de test ou d’acceptation qui omet cette étiquette restera ainsi exclu des résultats de recherche au lieu d’y apparaître.BesluitBron:Seo:NoIndexremplace la décision si nécessaire ; - Détermine si cette installation mentionne une adresse pour l'image du conteneur, sous
BesluitBron:Distribution:ImageReference. Si le champ est vide, cela signifie que la page « Exécuter soi-même » ne mentionne que le code source, ce qui est le cas pour une installation qui ne fournit rien en retour. À partir de la version 1.3.21 ; - Veiller à ce que l'hôte réserve au moins 2 Go de mémoire pour le conteneur. La composition de production fixe cette limite minimale pour le conteneur
bbn_web. En cas de pic de trafic, le service a besoin de cette mémoire ; avec moins, un pic de trafic entraînera un blocage. À partir de la version 1.3.21 ; - Définir une adresse de signalement des vulnérabilités sur
BesluitBron:SecurityTxt:Contact. Une adresse est préremplie ; un système qui lit les signalements ailleurs la modifie. Sans adresse, le service ne publie pas d'security.txts et les signalements ne sont pas pris en compte.
Chaque configuration mentionnée ci-dessus peut apparaître à plusieurs endroits, selon un ordre fixe dans lequel l'une remplace l'autre ; voir Instellingen Overschrijven.
Le fichier llms.txt
Le service publie le fichier « /llms.txt ». Ce fichier explique à un assistant IA, en langage naturel, en quoi consiste cette installation et quelles adresses MCP elle propose. Il est destiné à un assistant qui tombe sur le site sans qu’un connecteur ait été configuré. Il est indépendant du fichier « robots.txt », qui concerne les moteurs de recherche.
Le fichier respecte le format de llmstxt.org : un titre, un bref résumé, puis les remarques utiles au lecteur, et enfin des listes de références. À partir de la version 1.3.54, chaque adresse figurant dans ces listes est une véritable référence au format Markdown, suivie d’une explication. Jusqu’à la version 1.3.53 incluse, chaque adresse apparaissait sous forme de texte brut dans la ligne. Un assistant chargé de rechercher des références plutôt que de lire le texte n’en trouvait alors aucune. À partir de cette même version, le service indique également le jeu de caractères dans l’en-tête, afin que des noms tels que « Officiële Bekendmakingen » s’affichent correctement chez tous les lecteurs.
Le contenu provient du registre des connecteurs, voir Connectorregister. Une source désactivée n'y figure donc pas. Le fichier mentionne deux adresses par connecteur : l'adresse MCP elle-même et la page expliquant comment y connecter un assistant. Il indique également le nombre d'outils proposés par cette adresse et ce que couvre la source. Ces chiffres proviennent des adresses réellement mises en place et non d'une liste tenue par quelqu'un.
Vitesse des pages publiques
Une page publique s'affiche de la même manière pour tous les visiteurs. La langue est indiquée dans l'adresse et non pas par le visiteur. Le service enregistre donc la page générée et la fournit à toute personne qui accède à cette même adresse. Au bout de soixante secondes, le service régénère la page.
Cela s'applique à partir de la version 1.3.7 aux pages marketing, aux pages de connexion, à la documentation et aux sept explorateurs. Cela ne s'applique pas à /documentatie/zoeken, car chaque requête de recherche a sa propre réponse.
Deux règles garantissent la sécurité de la page enregistrée :
- Une requête envoyée n'est jamais affichée et ne remplit jamais la page. Un administrateur voit toujours une page actualisée ;
- Une réponse contenant un cookie associé à un seul visiteur n'est pas conservée. Seul le cookie de langue est autorisé, car il découle de l'adresse et est identique pour chaque visiteur accédant à cette adresse.
Une modification des paramètres est donc visible sur chaque page au plus tard au bout de soixante secondes. Une modification qui n'est calculée qu'au démarrage, comme l'activation ou la désactivation d'une source, nécessite toujours un redémarrage.
Santé
Il existe deux structures de santé, chacune ayant sa propre mission.
L'adresse /health signale chaque connecteur proposé séparément. Une plateforme inaccessible ne rend donc pas le service défaillant, mais bien le connecteur concerné. Ainsi, une panne chez BesluitBron peut être distinguée d'une panne chez un détenteur de source.
L'adresse /health/live sert à vérifier si le conteneur est actif. Elle indique uniquement si le processus est actif et capable de traiter des requêtes. À partir de la version 1.3.25, il n'effectue plus de requêtes auprès d'une plateforme source ou de la base de données. Une source lente ou inaccessible ne provoque donc pas le redémarrage du conteneur, car un redémarrage ne résoudrait pas le problème. L'évaluation des sources est disponible sur /health, qui est destiné à un administrateur et à un outil de surveillance. Même en cas de charge importante, /health/live reste accessible, y compris lorsque les adresses MCP sont saturées.
Limite de vitesse sur les adresses MCP
À partir de la version 1.3.25, une limite de vitesse est appliquée aux adresses MCP. Un client qui envoie des requêtes trop rapidement recevra un code d'erreur HTTP 429 avec le message « Retry-After ». L'administrateur n'a aucune configuration à effectuer : la limite est activée avec des valeurs par défaut généreuses qui n'affectent pas une utilisation normale. Une installation comptant de nombreux clients simultanés peut augmenter ces valeurs via BesluitBron:McpRateLimit. Voir Buffering en Doorlooptijd.