Aller au contenu
openplate

L'application

Auto-hébergement

Guides Compose pas à pas, premier compte, exécution sans Docker, premier lancement, HTTPS, sauvegardes, mise à jour

Cette page est traduite automatiquement à partir de la documentation en anglais.

openplate est distribué sous la forme d'une image Docker multi-architecture préconstruite (linux/amd64 + linux/arm64 ; les machines du type Raspberry Pi sont des cibles de premier ordre) publiée sur le GitHub Container Registry. L'étiquette latest correspond à la version la plus récente. Chaque version possède aussi sa propre étiquette de version. L'étiquette main suit chaque modification sur la branche principale. Il n'y a aucun secret à générer et rien auquel s'inscrire en dehors de ta propre clé de fournisseur d'IA.

Rien ici n'est une version allégée. Suivi local-first, analyse d'une photo d'assiette par IA avec ton propre fournisseur, installation PWA, export/import JSON, l'interface complète : tout est identique à l'instance hébergée, car c'est la même image. La seule différence avec l'instance hébergée est que nous faisons tourner le service de synchronisation optionnel pour toi, et celui-ci est aussi open source, pour que tu puisses l'héberger toi-même.

Ce que tu peux faire tourner

  • L'application seule. Ajoute un conteneur. Tu obtiens une installation rapide sans base de données ni secrets à gérer. Tu risques de perdre ton journal si tu effaces les données de ton navigateur, aucune synchronisation entre appareils n'est possible, et les analyses nécessitent une clé pour une IA dans le cloud.
  • L'application avec le serveur central. Ajoute openplate-core et Postgres. Tu obtiens une synchronisation chiffrée entre appareils et la possibilité de mutualiser les frais d'IA. Tu risques une perte de données si tu ne sauvegardes pas la base, le secret et ta clé de récupération.
  • L'application avec l'inférence auto-hébergée. Ajoute openplate-inference. Tu obtiens l'analyse locale des photos d'assiette, sans compte dans le cloud et sans aucune photo qui quitte ton réseau. Tu risques de surcharger le matériel, et chaque navigateur doit joindre le conteneur d'inférence directement.
  • Tout. La synchronisation et l'inférence ensemble, soit quatre conteneurs en tout. Tu obtiens la confidentialité totale de tes données avec la synchronisation multi-appareils. Tu t'exposes en contrepartie aux exigences d'exploitation et de ressources les plus lourdes.

Chaque profil correspond à un fichier compose dans docker/topologies/ ; topologies.md explique comment choisir.

Avant de commencer

Installe Docker. Un serveur vierge ne l'a pas. Suis le guide de Docker pour ta distribution sur docs.docker.com/engine/install. Sur Ubuntu 24.04, les paquets de la distribution conviennent aussi :

bash
sudo apt-get update
sudo apt install docker.io docker-compose-v2
sudo usermod -aG docker "$USER"   # then log out and back in, to use docker without sudo

Podman fonctionne aussi. Lis podman.md pour voir ce qui change.

Choisis un dossier persistant. Chaque guide ci-dessous commence par mkdir -p ~/openplate && cd ~/openplate. Le fichier compose et son .env s'y trouvent. Exécute toutes les commandes suivantes depuis cet endroit, y compris les mises à jour, les sauvegardes et la consultation des journaux. N'utilise pas /tmp. Un redémarrage peut le vider, et ton .env avec ses secrets disparaîtra.

Si tu fais tourner ceci pour ta famille, fais deux choses dès le début. - Sauvegarde .env et la base de données. Copie .env dans un endroit sûr, surtout la ligne SERVER_SECRET. Sans elle, une base de données restaurée n'ouvre aucun compte. Sauvegarde ensuite la base de données régulièrement. Sauvegardes contient les commandes. - Ne laisse les autres appareils accéder qu'au port HTTPS. De nombreux serveurs démarrent sans pare-feu, chaque port publié par un conteneur est donc ouvert à ton réseau. Garde les ports du conteneur sur 127.0.0.1, comme le montre la section HTTPS, afin que seul ton reverse proxy y accède. Un pare-feu comme ufw ne les ferme pas pour toi, car Docker publie ses ports en le contournant. ufw ferme tout de même tout le reste sur le serveur. Autorise SSH en premier, puis HTTPS : ``bash sudo ufw allow OpenSSH sudo ufw allow 443/tcp sudo ufw allow 8443/tcp # the core server's HTTPS port, in the recipes below sudo ufw enable ` With a domain name, Caddy also needs port 80 for its certificate: sudo ufw allow 80/tcp`.

L'application seule

Outil de conteneurs
mkdir -p ~/openplate && cd ~/openplate
curl -O https://raw.githubusercontent.com/LowCarbCheck/openplate/main/docker/compose.yml
docker compose -f compose.yml up -d
Ouvre-le en http://localhost:3000 sur le serveur lui-même, ou via HTTPS. Depuis un autre appareil, http://<the server's address>:3000 affiche le journal. L'installation de l'application, l'utilisation hors ligne et la connexion OpenRouter en un clic n'y fonctionnent pas. Consulte HTTPS.

Podman exécute ces fichiers avec podman compose. Sur Ubuntu, cette sous-commande exige le paquet podman-compose comme fournisseur : consulte podman.md.

Tu obtiens un seul conteneur, pas de base de données, pas d'étape .env, et aucun secret à générer. L'application est accessible sur http://localhost:3000.

Le port est publié sur l'interface chaque. L'application devient joignable sur l'adresse de réseau local de cette machine dès que up -d se termine. Sur un réseau partagé, remplace la ligne ports: par '127.0.0.1:3000:3000' et accède-y plutôt via un proxy inverse.

TRUST_PROXY définit le nombre de proxys inverses placés devant l'application. Le fichier compose utilise 1 par défaut. Cela convient derrière un proxy comme Caddy ou nginx. Sans cela, le contrôle CSRF de l'application lit la mauvaise adresse et l'envoi des formulaires échoue. Sans proxy en amont, règle la valeur sur 0 :

bash
echo "TRUST_PROXY=0" >> .env
docker compose -f compose.yml up -d

Sans proxy, les pages s'affichent avec l'une ou l'autre valeur. Cependant, 1 permet à un visiteur d'usurper son adresse dans X-Forwarded-For et de contourner la limite de recherche d'aliments par adresse.

Construire l'image soi-même

Pour construire depuis les sources au lieu de récupérer l'image publiée, commente image: dans docker/compose.yml, décommente build:, puis lance cette commande depuis la racine du dépôt, car le contexte de construction dépend de ce fichier :

Outil de conteneurs
docker compose --project-directory . -f docker/compose.yml build
docker compose --project-directory . -f docker/compose.yml up -d

Pour une exécution sans aucun conteneur, consulte Sans Docker.

Toute autre configuration (synchronisation, inférence en Auto-hébergement, ou les deux) se trouve dans un fichier séparé sous docker/topologies/. Consulte topologies.md pour faire ton choix.

L'application avec ton propre serveur central

docker/topologies/compose.core.yml constitue le déploiement de référence pour l'application, le serveur central et la base de données Postgres requise par sync. L'application ne se connecte toujours à aucune base de données qui lui soit propre. (Si tu souhaites aussi un Auto-hébergement de l'inférence, consulte L'application avec synchronisation et inférence en Auto-hébergement plus bas. Cette configuration utilise la même configuration de synchronisation, plus le moteur d'inférence du modèle.)

bash
mkdir -p ~/openplate && cd ~/openplate
curl -O https://raw.githubusercontent.com/LowCarbCheck/openplate/main/docker/topologies/compose.core.yml

# The core server needs exactly one secret. Generate it and keep it with your backups.
echo "SERVER_SECRET=$(openssl rand -hex 32)" >> .env

# Your key to the admin API. You need it to create the first account.
echo "ADMIN_TOKEN=$(openssl rand -hex 32)" >> .env

# The URLs a BROWSER will use to reach each service. Skip these two for a test
# on this machine, or through the ssh tunnel in the HTTPS section.
echo "PUBLIC_APP_URL=https://openplate.example.com" >> .env
echo "PUBLIC_SYNC_URL=https://sync.example.com" >> .env

# 1 behind one reverse proxy, 0 with none.
echo "TRUST_PROXY=1" >> .env

docker compose -f compose.core.yml up -d
bash
mkdir -p ~/openplate && cd ~/openplate
curl -O https://raw.githubusercontent.com/LowCarbCheck/openplate/main/docker/topologies/compose.core.yml

echo "SERVER_SECRET=$(openssl rand -hex 32)" >> .env
echo "ADMIN_TOKEN=$(openssl rand -hex 32)" >> .env

echo "PUBLIC_APP_URL=https://openplate.example.com" >> .env
echo "PUBLIC_SYNC_URL=https://sync.example.com" >> .env
echo "TRUST_PROXY=1" >> .env

podman compose -f compose.core.yml up -d
Les comptes nécessitent une page sécurisée. La connexion, l'inscription et l'ouverture d'un lien d'invitation échouent toutes sur un simple http://<the server's address> : le navigateur bloque les fonctions cryptographiques utilisées. Sers les deux adresses en HTTPS, ou teste via localhost. Consulte HTTPS.

Lis README d'openplate-core avant d'exécuter cette dernière ligne sur une machine accessible par d'autres personnes. Les deux services publient leurs ports sur toutes les interfaces. Le service de comptes est exposé dès son démarrage, et faire tourner un service de comptes est une tâche plus lourde que faire tourner l'application.

Le fichier est commenté ligne par ligne, y compris les deux réglages qui causent des problèmes s'ils sont mal configurés (SERVER_SECRET et TRUST_PROXY). TRUST_PROXY s'applique aux deux services, car ils se trouvent derrière le même proxy ou derrière aucun. Le fichier transmet chaque variable lue par les services depuis .env à leurs conteneurs. environment-variables.md les liste toutes. Consulte sync.md pour savoir ce qu'est la synchronisation et comment le client y accède.

Créer le premier compte

Personne ne peut s'inscrire de manière autonome. Un compte se crée en ouvrant une invitation envoyée à une adresse e-mail précise. Tu génères la première invitation pour toi-même sur le serveur à l'aide de ADMIN_TOKEN. Exécute cette commande dans ~/openplate avec ta propre adresse. Le port 3001 correspond à l'endroit où compose.core.yml et compose.full.yml publient le serveur central. Le fichier compose propre au serveur central dans apps/core utilise plutôt le port 3000 :

bash
ADMIN_TOKEN=$(grep '^ADMIN_TOKEN=' .env | cut -d= -f2)
curl -s -X POST http://127.0.0.1:3001/v1/admin/invites \
  -H "Authorization: Bearer $ADMIN_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"email":"you@example.com","displayName":"You","role":"admin"}'

La réponse est une ligne de JSON. La partie importante ressemble à ceci :

json
"emailed":false,"link":"https://openplate.example.com/join#server=https%3A%2F%2Fsync.example.com&invite=si_..."
  • Aucun serveur de messagerie configuré (la valeur par défaut) : "emailed": false. Aucun e-mail n'a été envoyé. Copie l'link et ouvre-la toi-même.
  • Serveur de messagerie configuré (voir Courriel) : "emailed": true. Le même lien est en cours d'envoi à cette adresse sous forme de courriel.

Ouvre le lien dans un navigateur sur une page sécurisée, choisis un mot de passe, et le compte est créé. Un téléphone ou un second appareil ne peut se connecter qu'après avoir configuré HTTPS, car le tunnel ssh et localhost ne desservent qu'un seul ordinateur. Le lien n'est valable qu'une fois et expire au bout de sept jours. "role":"admin" attribue les droits d'administrateur à ce premier compte. Par la suite, tu invites d'autres personnes directement dans l'application via /admin. Sur une instance sans messagerie, elle affiche chaque nouveau lien. Omets role pour un membre ordinaire. Courriel détaille les deux méthodes : transmettre chaque lien manuellement, ou laisser le serveur central l'envoyer par e-mail.

Sur une instance gérée, où le serveur central paie les analyses de tout le monde, ajoute "dailyAiLimit":200 au corps de la requête pour attribuer au compte 200 requêtes d'IA par jour. La valeur par défaut est 0. Une instance gérée a besoin de quatre lignes supplémentaires dans .env. Les analyses refusent de démarrer sans modèle, car l'application ne choisira aucun modèle à tes frais :

bash
echo "INSTANCE_MODE=managed" >> .env
echo "UPSTREAM_BASE_URL=https://openrouter.ai/api/v1" >> .env
echo "UPSTREAM_API_KEY=sk-or-..." >> .env
echo "AI_ADVERTISED_MODEL=vendor/model-name" >> .env

Remplace vendor/model-name par le modèle de ton fournisseur, orthographié exactement comme il l'écrit. Au lieu de AI_ADVERTISED_MODEL, tu peux définir AI_TIERS_FILE=bundled, et le serveur central récupère le modèle depuis son fichier de niveaux, ai-tiers.json. Voir configuration.md.

En cas d'oubli de mot de passe

Quand la messagerie est configurée, Mot de passe oublié dans l'application envoie un lien de réinitialisation par e-mail, et le journal réapparaît après la réinitialisation. Sans messagerie, l'application ne peut rien envoyer, et la page d'oubli indique à l'utilisateur de demander à l'administrateur. Tu crées le lien :

  • Dans l'application : Administration, sous Personnes, ouvre la personne et choisis Envoyer un lien de réinitialisation. Sans messagerie, la page affiche le lien. Partage-le comme tu partagerais un mot de passe.
  • Sur le serveur : trouve le id du compte, puis demande un lien pour celui-ci. Le port est à nouveau 3001, comme dans les fichiers compose de topologie.
bash
ADMIN_TOKEN=$(grep '^ADMIN_TOKEN=' .env | cut -d= -f2)
curl -s http://127.0.0.1:3001/v1/admin/accounts -H "Authorization: Bearer $ADMIN_TOKEN"
curl -s -X POST http://127.0.0.1:3001/v1/admin/accounts/1/reset-mail \
  -H "Authorization: Bearer $ADMIN_TOKEN"

La deuxième commande renvoie {"emailed":false,"link":"https://openplate.example.com/reset#server=...&token=sr_..."}. Un lien de réinitialisation fonctionne une seule fois et expire au bout d'une heure.

Courriel

Le serveur central peut envoyer des messages d'invitation et de réinitialisation de mot de passe. Cela reste facultatif. Une instance familiale fonctionne sans aucune configuration de messagerie, et c'est la voie la plus simple.

Sans courriel

Ne définis aucun paramètre de messagerie. Le serveur central n'envoie alors aucun message. Il t'affiche plutôt chaque lien, et tu le transmets comme tu partagerais un mot de passe.

  • Une invitation : ouvre Administration sous /admin et choisis Inviter quelqu'un. Saisis l'adresse et choisis Envoyer l'invitation. La page indique Invitation prête pour cette adresse et affiche le lien. Choisis Copier le lien et envoie-le à la personne, par exemple par message privé. Toute personne disposant du lien peut créer le compte.
  • Un mot de passe oublié : sous Personnes, ouvre la personne et choisis Envoyer un lien de réinitialisation. La page affiche le lien. Transmets-le de la même façon. Il fonctionne une seule fois, pendant une heure.
  • Une invitation perdue : sous Invitations, choisis Renvoyer à côté de l'adresse. Cela génère un nouveau lien et invalide l'ancien. La page affiche le nouveau lien à copier. Choisis Retour à la liste pour revenir à tes invitations en attente.

Si le lien utilise une adresse différente de celle de ton navigateur, un avertissement apparaît en dessous. Règle PUBLIC_APP_URL et PUBLIC_SYNC_URL sur les adresses qu'utilise ta famille, puis génère de nouveau le lien. La page t'avertit également si le lien permet d'ouvrir la page mais redirige l'application vers un serveur central situé sur localhost ou sur une simple adresse http:// inaccessible aux autres appareils. Règle PUBLIC_SYNC_URL sur l'adresse https:// qu'utilise ta famille, puis génère de nouveau le lien.

OPEN_SIGNUP=true permet à des inconnus de demander un compte. Le serveur central refuse de démarrer avec ce paramètre si aucun service de messagerie n'est configuré. Une instance familiale le laisse désactivé, et il le reste tant que tu ne l'actives pas. Inscription avec Turnstile explique ce paramètre et son captcha.

SMTP

Tout compte de messagerie standard peut envoyer les courriels via SMTP. Ajoute ces lignes dans .env :

  • SMTP_HOST : le nom du serveur, sans schéma ni port.
  • SMTP_PORT : 587 si tu l'omets.
  • SMTP_USER et SMTP_PASSWORD : les identifiants de connexion. Définis les deux, ou laisse les deux vides pour un serveur sans authentification.
  • SMTP_FROM : l'adresse d'expédition, sous forme d'adresse brute ou de Name <address>.
  • MAIL_OPERATOR_EMAIL : ta propre adresse. Elle reçoit ta copie d'une résiliation ou d'une rétractation. Les deux modes de transport l'exigent.

Le port détermine le mode de chiffrement. Le port 465 utilise TLS dès le départ. Tout autre port doit être mis à niveau via STARTTLS, et le service n'envoie aucune lettre à un serveur qui n'en dispose pas. Le texte en clair n'est autorisé que lorsque SMTP_HOST est une adresse de bouclage comme localhost, pour un intercepteur local comme Mailpit (voir Tester avec Mailpit). Le service vérifie toujours les certificats.

Un compte Gmail nécessite un mot de passe d'application. Google n'en génère que pour les comptes avec la validation en deux étapes activée. Paramètres SMTP de Google indique smtp.gmail.com et le port 587 :

bash
SMTP_HOST=smtp.gmail.com
SMTP_PORT=587
SMTP_USER=family.openplate@gmail.com
SMTP_PASSWORD="the app password"
SMTP_FROM="openplate <family.openplate@gmail.com>"
MAIL_OPERATOR_EMAIL=you@example.org

Amazon SES requiert des identifiants SMTP créés pour SES, qui diffèrent de ta clé d'accès AWS, ainsi qu'une adresse d'expédition vérifiée. L'hôte indique ta région AWS. Consulte la liste des points d'accès pour les détails. Tant que ton compte reste dans le bac à sable SES, SES distribue uniquement vers des adresses de destinataires vérifiées.

bash
SMTP_HOST=email-smtp.eu-central-1.amazonaws.com
SMTP_PORT=587
SMTP_USER=<SES SMTP user name>
SMTP_PASSWORD=<SES SMTP password>
SMTP_FROM="openplate <noreply@example.org>"
MAIL_OPERATOR_EMAIL=you@example.org

Recrée le serveur central avec docker compose -f <your file> up -d après toute modification de .env.

Une API HTTP pour le courrier

Un service de messagerie doté d'une API HTTP fonctionne tout aussi bien. Définis MAIL_API_URL, MAIL_API_KEY et MAIL_API_FROM, tous les trois, ainsi que MAIL_OPERATOR_EMAIL. Le serveur central transmet chaque message sous la forme d'une requête JSON POST adressée à MAIL_API_URL, avec MAIL_API_KEY comme jeton Bearer, selon le format attendu par l'API de Resend. Resend représente l'un des services compatibles. Sur Resend, MAIL_API_URL vaut https://api.resend.com/emails.

Configure un seul mode de transport. Si tu définis à la fois SMTP et l'API de messagerie, le serveur central refuse de démarrer.

Le courrier nécessite les adresses publiques

Chaque message contient un lien, et ce lien doit pouvoir s'ouvrir sur le téléphone du destinataire. Quand tu configures SMTP ou une API de messagerie, règle PUBLIC_APP_URL et PUBLIC_SYNC_URL sur les adresses https:// qu'utilise ta famille. Si l'un de ces paramètres emploie une simple adresse http:// ou une adresse de boucle locale comme localhost, le serveur central refuse de démarrer. Son journal indique chaque valeur à corriger, dans un message qui commence ainsi :

Mail is configured. Its messages would carry links that recipients cannot open.

Le message du journal désigne ces valeurs sous les noms CLIENT_BASE_URL et SERVER_PUBLIC_URL. Il s'agit des noms internes lus par le serveur central, et les fichiers compose les mappent depuis PUBLIC_APP_URL et PUBLIC_SYNC_URL. Consulte le message avec docker compose -f <your file> logs core.

Vérifier le bon fonctionnement du courrier

Envoie une invitation à une deuxième adresse t'appartenant dans /admin. La page doit afficher Invitation envoyée à cette adresse, et le message doit arriver dans ta boîte de réception. Si la page affiche Invitation prête pour et présente un lien, l'envoi a échoué. Le lien demeure valide. Vérifie le journal du serveur central pour repérer une ligne Mail send failed afin d'en connaître la cause. Dès les tests achevés, choisis Retirer pour l'invitation de test sous Invitations.

Tester avec Mailpit

Mailpit intercepte chaque message et l'affiche sur une page web, ce qui permet de tester la messagerie sans posséder de compte de messagerie. Fais-le tourner comme un conteneur d'accompagnement qui partage le réseau du conteneur central. Le serveur central le joint alors à l'adresse localhost, où le texte brut est permis. Enregistre ce fichier à côté de ton fichier compose sous le nom compose.mailpit.yml :

yaml
# compose.mailpit.yml: a mail catcher for testing, next to your compose file
services:
  core:
    ports:
      - '127.0.0.1:8025:8025' # Mailpit's web page, on this machine only
  mailpit:
    image: docker.io/axllent/mailpit:latest
    restart: unless-stopped
    network_mode: 'service:core'

Ajoute ces lignes à .env. Mailpit reçoit les lettres sur le port 1025 :

bash
SMTP_HOST=localhost
SMTP_PORT=1025
SMTP_FROM="openplate <test@example.org>"
MAIL_OPERATOR_EMAIL=you@example.org

Démarre les deux fichiers ensemble, puis envoie une invitation et lis-la sur http://localhost:8025 sur le serveur :

bash
docker compose -f compose.core.yml -f compose.mailpit.yml up -d

La règle énoncée dans Le courrier nécessite les adresses publiques s'applique toujours. Règle d'abord PUBLIC_APP_URL et PUBLIC_SYNC_URL sur des adresses https://, sans quoi le serveur central ne démarre pas.

Un conteneur Mailpit distinct accessible par son nom de service, tel que SMTP_HOST=mailpit, ne fonctionne pas. Le serveur central envoie du texte brut uniquement à cette machine. Un nom de service est considéré comme un autre hôte. Le service requiert STARTTLS, Mailpit n'en propose aucun, et chaque message échoue avec la mention Mail send failed dans le journal. Le réseau partagé place Mailpit sur la même machine.

Quand tu as fini de tester, supprime les quatre lignes de .env. Supprime ensuite Mailpit avec docker compose -f compose.core.yml up -d --remove-orphans.

Un relais avec une autorité de certification privée

Le serveur central vérifie le certificat de chaque serveur de messagerie. Un relais interne à un réseau d'entreprise peut utiliser un certificat signé par une autorité de certification privée. Node.js ne fait pas confiance à cette autorité par défaut. Fournis au serveur central le certificat de cette autorité sous la forme d'un fichier PEM. Monte le fichier dans le conteneur et renseigne son chemin dans NODE_EXTRA_CA_CERTS. Par exemple, dans un compose.ca.yml placé à côté de ton fichier compose :

yaml
# compose.ca.yml: trust a private certificate authority for the mail relay
services:
  core:
    volumes:
      - ./relay-ca.pem:/etc/openplate/relay-ca.pem:ro
bash
echo "NODE_EXTRA_CA_CERTS=/etc/openplate/relay-ca.pem" >> .env
docker compose -f compose.core.yml -f compose.ca.yml up -d

Node.js lit le fichier une seule fois, au démarrage. Le certificat s'ajoute à ceux auxquels Node.js fait déjà confiance, les serveurs de messagerie publics continuent donc de fonctionner.

L'application avec le moteur d'inférence auto-hébergé

docker/topologies/compose.inference.yml lance l'application aux côtés de openplate-inference. Les photos d'assiette sont analysées sur ton propre matériel, et chaque visiteur dispose d'un bouton "cet openplate fournit sa propre IA". Lis d'abord la section sur le matériel dans topologies.md. Le petit modèle lite demande environ 1,6 Go de RAM et de quelques secondes à une minute par assiette sur un processeur.

bash
mkdir -p ~/openplate && cd ~/openplate
curl -O https://raw.githubusercontent.com/LowCarbCheck/openplate/main/docker/topologies/compose.inference.yml

# One key, generated here on the server. The inference service accepts it and
# the app hands it to every browser.
echo "INFERENCE_API_KEY=opk_$(openssl rand -hex 24)" >> .env

# The two URLs a BROWSER will use. Replace 192.168.1.20 with this machine's
# address, or with the names your reverse proxy serves.
echo "PUBLIC_APP_URL=http://192.168.1.20:3000" >> .env
echo "PUBLIC_INFERENCE_URL=http://192.168.1.20:8300/v1" >> .env

# 0 with no reverse proxy, 1 behind one.
echo "TRUST_PROXY=0" >> .env

docker compose -f compose.inference.yml up -d
docker compose -f compose.inference.yml logs -f inference
Le simple HTTP convient pour les scans, le HTTPS exige du HTTPS de bout en bout. En simple http://<the server's address>, une photo d'assiette parvient au conteneur d'inférence, mais l'installation de la PWA ne fonctionne pas. Dès que l'application tourne sur https://, l'adresse d'inférence doit être en https:// elle aussi. Sinon, le navigateur bloque l'appel émis depuis la page sécurisée. Consulte HTTPS.

PUBLIC_INFERENCE_URL doit être une adresse qu'un navigateur peut ouvrir, car la photo part du téléphone directement vers le conteneur d'inférence. http://inference:8300/v1, le nom que les conteneurs utilisent entre eux, ne fonctionne pas ici. Conserve le /v1 à la fin.

Le premier démarrage télécharge environ 2 Gio de poids (1,96 Gio) dans un volume nommé, ce qui a pris six à sept minutes lors de nos tests, puis charge le modèle. Le journal affiche chaque étape. L'application est immédiatement prête, et l'IA en un geste fonctionne dès que ceci renvoie 200 :

bash
curl -s http://127.0.0.1:8300/readyz

La clé se trouve dans la page chargée par chaque navigateur, donc toute personne pouvant ouvrir l'application peut la lire. Cela convient sur un réseau domestique ou un tailnet, mais pas sur une instance ouverte sur Internet. configuration.md détaille la règle complète. Le conteneur d'inférence utilise tous les cœurs du processeur sauf deux, définis LLAMA_THREADS dans .env pour modifier cela.

L'application avec synchronisation et inférence en Auto-hébergement

docker/topologies/compose.full.yml exécute quatre conteneurs : l'application, le serveur central, Postgres et le moteur d'inférence en Auto-hébergement. Lis d'abord L'application avec ton propre serveur central et L'application avec le moteur d'inférence auto-hébergé. Cette section traite uniquement des changements apportés quand tous les éléments tournent ensemble.

bash
mkdir -p ~/openplate && cd ~/openplate
curl -O https://raw.githubusercontent.com/LowCarbCheck/openplate/main/docker/topologies/compose.full.yml

# The core server needs exactly one secret. Generate it and keep it with your backups.
echo "SERVER_SECRET=$(openssl rand -hex 32)" >> .env

# Your key to the admin API. You need it to create the first account.
echo "ADMIN_TOKEN=$(openssl rand -hex 32)" >> .env

# One key for the inference service, which the app hands to every browser.
echo "INFERENCE_API_KEY=opk_$(openssl rand -hex 24)" >> .env

# The URLs a BROWSER will use to reach each service. PUBLIC_APP_URL and
# PUBLIC_SYNC_URL default to localhost, so skip both for a test on this
# machine. PUBLIC_INFERENCE_URL has no such default: set it even for a
# local test, for example to http://localhost:8300/v1.
echo "PUBLIC_APP_URL=https://openplate.example.com" >> .env
echo "PUBLIC_SYNC_URL=https://sync.example.com" >> .env
echo "PUBLIC_INFERENCE_URL=https://ai.example.com/v1" >> .env

# 1 behind one reverse proxy, 0 with none.
echo "TRUST_PROXY=1" >> .env

docker compose -f compose.full.yml up -d

Crée le premier compte de la même façon que dans ci-dessus. Le premier démarrage télécharge aussi les poids d'inférence, soit environ 2 Gio. Le journal de chargement du modèle et la vérification de disponibilité fonctionnent comme dans L'application avec le moteur d'inférence auto-hébergé. Pour de vrais appareils plutôt qu'un test, place les trois adresses derrière HTTPS. Voir HTTPS.

Sans Docker

L'application est un simple programme Node.js. Elle s'exécute directement depuis une copie du dépôt. Voici comment l'exécuter sur Ubuntu 24.04 avec systemd pour la maintenir active après les déconnexions et les redémarrages.

Node.js 24 ou plus récent. Ubuntu 24.04 fournit la version 18 de nodejs, ce qui est trop ancien. Installe la version 24 depuis NodeSource :

bash
curl -fsSL https://deb.nodesource.com/setup_24.x -o nodesource_setup.sh
sudo -E bash nodesource_setup.sh
sudo apt install -y nodejs git
node --version      # v24.x

nvm fonctionne aussi, mais il place Node dans ton dossier personnel. L'unité systemd ci-dessous doit alors utiliser ce chemin (command -v node l'affiche).

pnpm, la version demandée par le dépôt. corepack est fourni avec Node 24. Il télécharge la version exacte de pnpm définie dans le champ packageManager du fichier package.json de l'application, mais uniquement à l'intérieur de apps/app. En dehors de ce dossier, pnpm utilise par défaut le choix de corepack. Exécute chaque commande pnpm dans apps/app. Au premier lancement, confirme l'invite de téléchargement.

bash
sudo corepack enable
git clone https://github.com/LowCarbCheck/openplate.git ~/openplate-src
cd ~/openplate-src/apps/app
pnpm install --frozen-lockfile
pnpm build

Les paramètres. Le serveur lit .env depuis le dossier où il tourne. En production, il refuse de démarrer sans APP_URL :

bash
cat > .env <<'EOF'
NODE_ENV=production
PORT=3000
APP_URL=http://localhost:3000
TRUST_PROXY=0
EOF

Définis APP_URL sur l'adresse que les gens ouvrent, et TRUST_PROXY=1 dès qu'un reverse proxy est placé devant. Chaque autre variable de l'application va dans le même fichier. HOST=127.0.0.1 fait écouter le serveur uniquement sur cette machine, ce qui correspond à ce que tu veux derrière un proxy sur la même machine.

Une unité systemd, pour que l'application démarre au boot et continue de tourner après ta déconnexion. $USER et $HOME ci-dessous sont remplis quand tu le colles :

bash
sudo tee /etc/systemd/system/openplate.service > /dev/null <<EOF
[Unit]
Description=openplate
After=network-online.target
Wants=network-online.target

[Service]
User=$USER
WorkingDirectory=$HOME/openplate-src/apps/app
Environment=NODE_ENV=production
ExecStart=/usr/bin/node --import tsx ./server.ts
Restart=on-failure

[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload
sudo systemctl enable --now openplate
curl -s http://127.0.0.1:3000/healthcheck

sudo journalctl -u openplate -f affiche le journal. Pour mettre à jour, récupère les changements, compile à nouveau, puis redémarre :

bash
cd ~/openplate-src/apps/app
git pull
pnpm install --frozen-lockfile
pnpm build
sudo systemctl restart openplate

La section HTTPS s'applique ici sans changement, pointe le reverse proxy vers le port 3000.

Premier lancement

  1. Ouvre l'application et suis la brève introduction. Pas d'inscription, pas de connexion : la personne qui ouvre l'application sur un appareil en est l'utilisateur.
  2. Rends-toi dans Paramètres → IA et configure un fournisseur d'IA avec ta propre clé API (OpenRouter, Mistral, ton propre point de terminaison compatible OpenAI, ou Anthropic). Consulte configuration.md pour utiliser le flux en un clic d'OpenRouter ou pour proposer à la place un point de terminaison géré par l'instance.
  3. Fais vite une sauvegarde : Paramètres → Données et sauvegarde → Tout télécharger (JSON). Ton journal est stocké dans ce navigateur, une sauvegarde est donc la seule copie qui survit à la suppression des données du site ou à un changement d'appareil.
  4. Si plusieurs personnes scannent sur cette instance, obtiens une clé gratuite de base alimentaire sur lowcarbcheck.org/developers, ajoute-la dans .env sous le nom FOOD_DB_API_KEY=..., et relance docker compose -f <your file> up -d. Sans clé, tout le monde sur l'instance partage un même petit quota anonyme. Voir configuration.md. Avec une clé, tu peux aussi définir FOOD_DB_BACKFILL=true, qui transmet à LowCarbCheck, comme propositions, les aliments enregistrés depuis une réponse de l'IA. Voir configuration.md.

HTTPS

Les navigateurs limitent plusieurs fonctionnalités à un contexte sécurisé. Un contexte sécurisé est une page servie via https://, ou depuis localhost sur la machine locale. openplate exige un contexte sécurisé pour plusieurs fonctions :

  • Comptes, connexion et synchronisation. La connexion, l'inscription ainsi que l'ouverture d'un lien d'invitation ou de réinitialisation dérivent des clés avec la Web Crypto API (crypto.subtle). Les navigateurs désactivent cette API sur une page http:// simple. En http://192.168.1.20:3000, ces écrans échouent. Le partage et la console de recherche échouent aussi, car ils utilisent la même API.
  • Se connecter avec OpenRouter, la connexion en un clic à OpenRouter, pour la même raison. Coller une clé à la main fonctionne partout.
  • Installer l'application et utilisation hors ligne (le service worker).

En HTTP simple depuis un autre appareil, le journal, la saisie manuelle, les sauvegardes et les photos d'assiette fonctionnent toujours. Le bouton photo bascule vers l'appareil photo du téléphone via un sélecteur de fichiers, ce qui ne requiert aucune page sécurisée. Le démarrage rapide fonctionne sans modification sur le serveur lui-même, car localhost est considéré comme sécurisé.

Tes appareils ont besoin d'une adresse sécurisée. Tu peux en configurer une de quatre manières : un tunnel ssh pour un test rapide, Caddy avec un nom de domaine, Caddy sur un réseau domestique sans nom de domaine, ou Tailscale.

Un test rapide depuis un ordinateur, un tunnel ssh

C'est un test pour un ordinateur, pas une installation définitive. Le tunnel ne sert qu'à l'ordinateur qui l'exécute, et seulement tant que la commande tourne. Un téléphone ou un second appareil ne peut pas s'y connecter. Pour cela, configure HTTPS avec Caddy plus bas.

Pour tester les comptes et la synchronisation avant de configurer un certificat, redirige les deux ports vers ton ordinateur. Laisse PUBLIC_APP_URL et PUBLIC_SYNC_URL non définis pour que tous deux conservent leurs valeurs par défaut localhost. Laisse également la messagerie non configurée ici. Si la messagerie est configurée, le serveur central refuse de démarrer dès lors que les adresses de ses liens mentionnent localhost. Sans messagerie, il démarre, et tu copies chaque lien toi-même. Exécute cette commande sur ton ordinateur, pas sur le serveur :

bash
ssh -N -L 3000:localhost:3000 -L 3001:localhost:3001 you@192.168.1.20

Pendant que la commande tourne, ouvre http://localhost:3000 dans ton navigateur. C'est considéré comme une page sécurisée, donc la connexion fonctionne. Un lien d'invitation généré par le serveur (http://localhost:3000/join#...) s'ouvre aussi ici. Ajoute -L 8300:localhost:8300 pour le conteneur d'inférence.

Un nom de domaine, Caddy

Caddy récupère et renouvelle automatiquement un certificat Let's Encrypt. Il nécessite un nom de domaine pointant vers ton serveur. Il nécessite aussi que les ports 80 et 443 soient accessibles depuis Internet pour la validation du certificat.

# Caddyfile
openplate.example.com {
    reverse_proxy localhost:3000
}

Ensuite, définis APP_URL=https://openplate.example.com dans .env. Définis TRUST_PROXY=1, qui est la valeur par défaut en production. Recrée le service app avec docker compose -f compose.yml up -d. Un simple docker compose restart ne relit pas .env. Il redémarre seulement le conteneur existant, donc les nouvelles valeurs ne sont jamais chargées.

Pour la synchronisation, attribue au serveur central son propre nom de domaine. Définis les deux URL publiques au lieu de APP_URL :

# Caddyfile
openplate.example.com {
    reverse_proxy localhost:3000
}
sync.example.com {
    reverse_proxy localhost:3001
}
bash
PUBLIC_APP_URL=https://openplate.example.com
PUBLIC_SYNC_URL=https://sync.example.com
TRUST_PROXY=1

Remplace la ligne ports: par '127.0.0.1:3000:3000', et utilise '127.0.0.1:3001:3000' pour la synchronisation. Applique la modification avec docker compose -f <your file> up -d. Un reverse proxy ne dépublie pas les ports des conteneurs. Si tu le laisses sous la forme '3000:3000', l'application continue de servir en simple HTTP sur le port 3000 à travers ton réseau local, en parallèle de l'adresse HTTPS.

Podman recrée le service de la même façon avec podman compose -f compose.yml up -d. Note un détail concernant le mode sans racine avant de te passer du reverse proxy : un conteneur Podman sans racine ne peut pas écouter sur un port hôte inférieur à 1024 sans configuration supplémentaire. Publier directement sur le port 80 ou 443 nécessite d'abord sudo sysctl net.ipv4.ip_unprivileged_port_start=80. Voir podman.md.

Sans nom de domaine, réseau domestique uniquement : Caddy avec un certificat local

Sans nom de domaine, Caddy peut tout de même servir HTTPS sur ton réseau domestique. Il crée sa propre autorité de certification et l'utilise pour signer un certificat pour l'adresse du serveur. Chaque téléphone et ordinateur qui ouvre openplate doit faire confiance à cette autorité une fois. Après cela, les téléphones de la famille obtiennent une page sécurisée. La connexion, la synchronisation et l'installation de l'application fonctionnent via https://.

Donne une adresse fixe au serveur. Dans ton routeur, réserve l'adresse actuelle du serveur, par exemple 192.168.1.20, pour qu'elle ne change jamais. Le certificat et les deux adresses publiques la mentionnent.

Installe Caddy et oriente-le vers l'adresse. Sur Ubuntu, sudo apt install caddy installe Caddy en tant que service. Remplace /etc/caddy/Caddyfile par ceci, en utilisant l'adresse de ton serveur. tls internal indique à Caddy de signer lui-même le certificat :

# /etc/caddy/Caddyfile
https://192.168.1.20 {
    tls internal
    reverse_proxy localhost:3000
}
https://192.168.1.20:8443 {
    tls internal
    reverse_proxy localhost:3001
}
bash
sudo systemctl reload caddy

Définis les mêmes adresses dans .env, puis recrée les conteneurs avec docker compose -f <your file> up -d :

bash
PUBLIC_APP_URL=https://192.168.1.20
PUBLIC_SYNC_URL=https://192.168.1.20:8443
TRUST_PROXY=1

Pour l'application seule, définis plutôt APP_URL=https://192.168.1.20. Omets le second bloc du Caddyfile. Comme dans la recette ci-dessus, remplace les lignes ports: par '127.0.0.1:3000:3000' et '127.0.0.1:3001:3000', pour que seul Caddy atteigne les conteneurs.

Copie le certificat racine de Caddy hors du serveur. La version de Caddy pour Ubuntu le conserve dans /var/lib/caddy/.local/share/caddy/pki/authorities/local/root.crt. Le certificat est public. Sa clé privée se trouve dans le même dossier et ne doit jamais quitter le serveur, alors copie uniquement root.crt :

bash
sudo cp /var/lib/caddy/.local/share/caddy/pki/authorities/local/root.crt ~/openplate-root.crt
sudo chown "$USER" ~/openplate-root.crt

Sur ton ordinateur, récupère-le avec scp you@192.168.1.20:openplate-root.crt .. Envoie-le ensuite à chaque téléphone, par exemple en pièce jointe d'un e-mail à toi-même, ou avec AirDrop.

Fais-lui confiance sur chaque appareil, une seule fois. C'est l'étape qui permet aux téléphones de la famille de fonctionner via https://.

  • iPhone et iPad : ouvre le fichier et autorise le téléchargement. Dans Paramètres, touche Profil téléchargé vers le haut, ou trouve-le sous Général > VPN et gestion des appareils, puis installe-le. Active ensuite la confiance totale pour celui-ci sous Réglages > Général > Informations > Réglages d'approbation des certificats. Sans cette dernière étape, le navigateur refuse toujours la page.
  • Android : enregistre le fichier sur le téléphone. Ouvre Paramètres > Sécurité > Chiffrement et identifiants > Installer un certificat > Certificat CA, accepte l'avertissement, et choisis le fichier. Sur les téléphones plus récents, le chemin commence à Sécurité et confidentialité > Autres paramètres de sécurité, et les libellés varient légèrement selon les constructeurs. Si le téléphone n'a pas de verrouillage d'écran, Android te demande d'en définir un.
  • Un ordinateur : ajoute-le aux certificats du système. Certains navigateurs conservent leur propre liste et en ont aussi besoin à cet endroit.

Ouvre https://192.168.1.20 sur un téléphone. La page devrait se charger sans avertissement, et la connexion devrait fonctionner. L'adresse fonctionne uniquement sur ton réseau local. Conserve le dossier de données de Caddy avec tes sauvegardes. Une nouvelle installation de Caddy crée une nouvelle autorité, et chaque appareil doit alors faire confiance à la nouvelle.

Tout autre proxy inverse

nginx, Traefik, ou un autre proxy peut remplacer Caddy. Nous n'en avons testé aucun, il s'agit donc d'une liste de points de contrôle, non d'une recette. Le proxy doit effectuer tout ceci :

  • Deux adresses https://. L'application et le serveur central obtiennent chacun la leur.
  • Les mêmes adresses dans .env. Définis PUBLIC_APP_URL et PUBLIC_SYNC_URL sur ces adresses exactes. Pour l'application seule, il s'agit de APP_URL.
  • TRUST_PROXY=1, ou le nombre de proxys dans la chaîne.
  • Host et X-Forwarded-Proto parviennent à l'application. Transmets l'en-tête Host du navigateur sans modification, ou attribue sa valeur à X-Forwarded-Host. Définis X-Forwarded-Proto à https. La vérification CSRF de l'application reconstruit l'adresse de la page à partir de ces éléments et la compare avec Origin du navigateur. S'ils sont incorrects, les envois de formulaires échouent.
  • X-Forwarded-For parvient aux deux services. Leurs limites par adresse l'utilisent.
  • Les ports des conteneurs restent sur 127.0.0.1. Conserve '127.0.0.1:3000:3000' et '127.0.0.1:3001:3000', afin que rien ne les atteigne en contournant le proxy.

Pas de nom de domaine, Tailscale Serve

Tailscale attribue à chaque machine de ton tailnet une adresse HTTPS sous ts.net. Tu n'as besoin d'aucun nom de domaine, d'aucun port ouvert ni d'aucune gestion manuelle de certificats. Tailscale Serve place cette adresse devant un port de cette machine. openplate avec synchronisation requiert deux adresses. Tu sers deux ports sous le même nom de machine : l'application sur le 443 et le serveur central sur le 8443.

Avant de commencer :

  • Active MagicDNS et les certificats HTTPS pour ton tailnet sur la page DNS de la console d'administration Tailscale. Guide HTTPS de Tailscale décrit les étapes. Le nom de la machine apparaît dans un registre public de certificats, choisis donc un nom qui ne révèle rien de privé.
  • Chaque membre de la famille utilise Tailscale sur chaque appareil qui ouvre openplate. Chaque personne doit faire partie de ton tailnet, ou avoir cette machine partagée avec lui.
  • Garde les ports des conteneurs sur 127.0.0.1 : '127.0.0.1:3000:3000' pour l'application et '127.0.0.1:3001:3000' pour la synchronisation. Tailscale Serve les joint sur cette machine. Rien d'autre n'a besoin d'y accéder.

Sers ensuite les deux ports. --bg les maintient en arrière-plan, et Tailscale les sert à nouveau après un redémarrage :

bash
tailscale serve --bg --https=443 3000
tailscale serve --bg --https=8443 3001
tailscale serve status

Tailscale émet et renouvelle le certificat. Définis les deux adresses dans .env, en utilisant les noms de ta propre machine et de ton tailnet :

bash
PUBLIC_APP_URL=https://<machine-name>.<tailnet>.ts.net
PUBLIC_SYNC_URL=https://<machine-name>.<tailnet>.ts.net:8443
TRUST_PROXY=1

Tailscale Serve est un reverse proxy, TRUST_PROXY reste donc à 1. Recrée la pile avec docker compose -f <your file> up -d. Si tu exécutes l'application seule, sers uniquement le port 3000 et définis APP_URL sur la première adresse.

Nous n'avons pas testé ce chemin de bout en bout. La Référence tailscale serve documente les options ci-dessus, et la documentation de Tailscale décrit tailscale serve. La recette Caddy ci-dessus est celle que nous avons vérifiée.

Headscale, un serveur de contrôle Tailscale auto-hébergé, ne délivre aucun certificat HTTPS. Un tailnet Headscale nécessite à la place la recette Caddy avec un domaine.

Sauvegardes

Il n'y a rien à sauvegarder sur le serveur de l'application. Il ne contient aucune base de données et n'enregistre aucun état : la destruction d'un conteneur d'application ne fait rien perdre.

L'export JSON par appareil constitue la vraie sauvegarde : Paramètres → Données et sauvegarde → Tout télécharger (JSON). Ce fichier constitue la copie qui survit à un nettoyage de navigateur ou à un téléphone hors d'usage. L'application affiche un bandeau de rappel lorsqu'un appareil contient des données que tu n'as jamais exportées, ou pas exportées depuis un certain temps.

Garde l'export aussi privé que le journal. Il contient chaque entrée en clair, ainsi que la clé privée de l'identité de partage de cet appareil et la racine dont dérive le pseudonyme de recherche. Quiconque possède le fichier peut ouvrir un journal partagé avec cette personne, et peut relier ses contributions à l'étude à son identité. Deux éléments en restent exclus : la clé du fournisseur d'IA, et les photos d'assiette, qui ne quittent jamais l'appareil.

Si tu fais aussi tourner le serveur central, son instance Postgres mérite une sauvegarde planifiée, tout comme le SERVER_SECRET, lequel ne sert à rien sans la base de données et réciproquement :

Outil de conteneurs
docker compose -f compose.core.yml exec postgres \
  pg_dump -U openplate openplate_sync > sync-backup.sql

Mettre à niveau

Outil de conteneurs
docker compose -f compose.yml pull
docker compose -f compose.yml up -d

Utilise le même fichier -f que lors de ton déploiement. Si tu as démarré une topologie depuis docker/topologies/, indique plutôt ce fichier, par exemple docker compose -f compose.core.yml pull. Une simple commande docker compose pull à côté de compose.core.yml échoue avec no configuration file provided: not found.

Les fichiers compose utilisent l'étiquette latest, qui correspond à la dernière version, pull te permet donc d'y accéder. Pour choisir le moment de la mise à jour, fige une version sur la ligne image:, par exemple ghcr.io/lowcarbcheck/openplate:0.54.0. Change le numéro quand tu veux passer à la version suivante. Le serveur central et le service d'inférence ont leurs propres numéros de version, fige donc chaque image sur la sienne. L'étiquette main suit chaque modification sur la branche principale. Elle sert aux tests, pas à un serveur qu'utilise ta famille.

Il n'y a rien à migrer : le conteneur de l'application ne conserve aucun état, une nouvelle image remplace donc simplement l'ancienne. Si tu fais tourner toute l'infrastructure, le serveur central applique ses propres migrations au démarrage.

Mettre à niveau depuis une image antérieure à 0.1.x (une seule fois)

Les anciennes images utilisaient leur propre système de comptes et leur propre Postgres. Les deux ont disparu. La mise à niveau supprime la table users et tout ce qui en dépend : comptes, sessions, jetons de vérification et de réinitialisation. Rien de ce que tu as consigné n'est affecté : les données de suivi avaient déjà migré sur l'appareil. C'est un changement irréversible, donc :

  1. Sauvegarde d'abord. Fais un pg_dump de l'ancienne base de données de l'application si tu veux pouvoir récupérer les lignes de comptes, et demande à chaque personne d'effectuer un export JSON depuis Profil → Tes données sur chaque appareil. Cet export est la copie qui conserve leur journal.
  2. Mettre à niveau. Mets d'abord à jour la ligne image: vers ghcr.io/lowcarbcheck/openplate:latest, la version la plus récente : les images antérieures à 0.1.x étaient publiées sous ghcr.io/sprqvntrs/openplate, et lancer pull sans ce changement ne fait que récupérer l'ancienne. La migration s'exécute ensuite au démarrage du conteneur. Après cela, il n'y a plus de page de connexion : chaque appareil possédant déjà des données les conserve et cesse simplement de demander qui tu es.
  3. Nettoie ton fichier .env. Les variables de secret de session, de clé de chiffrement, de restriction d'inscription et de super-administrateur initial ont disparu, tout comme DB_HOST, DB_PORT, DB_USER, DB_PASSWORD, DB_NAME et les variables de réglage du pool. Plus rien ne les lit. Les laisser définies ne pose pas de problème, mais c'est du poids mort.
  4. Supprimer l'ancien volume une fois que tout te convient. Les anciennes versions ne figeaient aucun nom de projet Compose, le préfixe venait donc du répertoire dans lequel tu te trouvais (vérifie d'abord le vrai nom) : docker volume ls | grep pg-data, puis docker compose down && docker volume rm <that name>.

Si deux comptes étaient connectés sur le même profil de navigateur, note que le stockage sur l'appareil a toujours été limité à l'appareil : leurs données partageaient déjà un même espace et cela ne change pas. Utiliser des profils de navigateur distincts reste le moyen de séparer les journaux de deux personnes sur un même appareil.

Modifier cette page sur GitHub