Skip to content

Déploiement VPS via Tailscale (sans domaine, sans TLS) ​

Objectif : faire passer le backend TCG du PC Windows au VPS, continuer à y accéder depuis le téléphone via Tailscale, et servir les images de cartes depuis notre propre nginx au lieu de TCGdex.

C'est le setup Option B — le chemin le plus rapide vers "VPS only" sans acheter de domaine ni configurer Let's Encrypt. Tout le tunnel entre le téléphone et le VPS reste à l'intérieur de votre tailnet Tailscale, donc HTTP en clair est acceptable.

Pour le setup production / domaine public (HTTPS via Caddy), voir le guide jumeau vps-public-deployment.md (à écrire quand vous serez prêt à quitter Tailscale).

Ce que veut dire "Option B" en pratique ​

Aujourd'huiAprès ce guide
Où tourne le backendPC Windows, Docker DesktopVPS (Linux)
Comment le téléphone l'atteinthttp://100.76.15.57:8000/scan-compat (IP tailnet du PC)http://<IP_TAILNET_VPS>:8000/scan-compat
D'où viennent les images de cartesCDN TCGdex (assets.tcgdex.net)Notre propre nginx sur le VPS, alimenté depuis data/images/
Pré-requis téléphoneTailscale activéTailscale activé (même contrainte)
Exposition publiqueAucuneAucune — les services bind sur l'IP tailnet uniquement

Vous gardez la dépendance Tailscale sur le téléphone (déjà le cas aujourd'hui), mais le laptop n'est plus dans la boucle.

Pré-requis ​

  • Le VPS a déjà Docker Engine + plugin compose et le repo snag-tcg/tcg-collector-backend cloné.
  • La PR qui introduit LOCAL_IMAGE_BASE_URL (#92) est mergée dans main.
  • Vous avez accès admin à votre tailnet Tailscale (pour ajouter le VPS comme node).
  • Le téléphone est déjà sur le tailnet (c'est le cas, puisque le setup actuel marche).

Étape 1 — Installer Tailscale sur le VPS ​

Sur le VPS :

bash
curl -fsSL https://tailscale.com/install.sh | sh
sudo tailscale up
# Suivez l'URL imprimée dans un navigateur pour authentifier le node sur votre tailnet.
# Taggez-le (console admin Tailscale → Machines) p. ex. "tcg-vps" pour la lisibilité.

# Notez l'IP tailnet — vous en aurez besoin partout désormais.
tailscale ip -4
# p. ex. 100.124.7.91

À partir d'ici ce guide utilise <IP_TAILNET_VPS> comme placeholder pour cette IP. Remplacez par votre vraie valeur (ou exportez IP_TAILNET_VPS=100.124.7.91 dans votre shell pour copier-coller les blocs sans modification).

Sanity check depuis le laptop (aussi sur le tailnet) :

bash
ping -c 3 <IP_TAILNET_VPS>
ssh kael@<IP_TAILNET_VPS>   # vérifier que SSH marche toujours via le tailnet

Étape 2 — Pull du dernier backend sur le VPS ​

SSH dans le VPS :

bash
cd ~/snag-tcg/tcg-collector-backend
git checkout main
git pull

Confirmer que les nouvelles env vars sont bien visibles dans .env.example (ajoutées par la PR #92) :

bash
grep -A 2 LOCAL_IMAGE_BASE_URL .env.example

Étape 3 — Configurer .env sur le VPS ​

Ne pas copier le .env du laptop tel quel. Plusieurs valeurs changent (LOCAL_IMAGE_BASE_URL doit pointer sur l'IP tailnet du VPS, pas du laptop ; CORS_ORIGIN peut différer ; etc.), et autant traiter le VPS comme un environnement séparé avec ses propres secrets. Le seul fichier .env qui compte côté VPS est celui de tcg-collector-backend — poc-tcg-collector reste sur le laptop, documentation n'a pas de secrets.

Exception : si à l'Étape 6 vous décidez de transférer data/pg/ du laptop au lieu de relancer un ingest frais (~30 min gagnées), alors PGPASSWORD côté VPS doit être identique à celui du laptop, sinon Postgres refuse de monter le volume. Par défaut ce guide fait un ingest frais → cette contrainte ne s'applique pas, générez des secrets neufs.

bash
cp .env.example .env   # uniquement si .env n'existe pas encore

# Générer des secrets frais directement sur le VPS, et les sauvegarder
# dans votre password manager AVANT d'éditer .env. Une fois Postgres
# initialisé avec PGPASSWORD, le changer demande de wiper data/pg/.
openssl rand -hex 32   # → PGPASSWORD
openssl rand -hex 32   # → API_WRITE_TOKEN

nano .env

À régler au minimum :

bash
# Postgres — choisir un mot de passe fort AVANT le premier `compose up`.
# Une fois que Postgres a initialisé data/pg/ avec ce mot de passe, le
# changer demande soit un ALTER USER dans le container qui tourne, soit
# de wiper le dossier data. Donc autant le faire bien dès le départ.
PGUSER=tcg
PGPASSWORD=<chaine-aleatoire-longue>

# API
API_WRITE_TOKEN=<chaine-aleatoire-longue>
CORS_ORIGIN=*   # acceptable sur un tailnet ; à resserrer quand on passera sur un domaine

# tcg-vision — images de cartes self-hosted.
# IMPORTANT : doit être joignable *depuis le téléphone*, donc utilisez
# l'IP tailnet du VPS, pas localhost (le téléphone résout cette URL
# après le scan).
LOCAL_IMAGE_BASE_URL=http://<IP_TAILNET_VPS>:8081/images
LOCAL_IMAGE_EXT=webp
LOCAL_IMAGE_QUALITY=high

# Ingest — premier run. Adaptez INGEST_SETS aux sets que vous voulez.
INGEST_SETS=base1,sv01
INGEST_IMAGES=true
INGEST_IMAGE_QUALITY=low,high

Étape 4 — Binder les ports Docker sur l'IP tailnet uniquement ​

Par défaut Docker publie les ports sur 0.0.0.0, ce qui veut dire que n'importe qui sur internet qui trouve le VPS peut taper sur :8000, :8080, :8081, :5432. On veut qu'ils soient atteignables uniquement sur le tailnet.

Éditer docker/compose.yml et préfixer chaque mapping ports: avec l'IP tailnet. Exemple pour tcg-vision :

yaml
ports:
  - "<IP_TAILNET_VPS>:8000:8000"   # avant : "${TCG_VISION_PORT:-8000}:8000"

Faire pareil pour nginx (<IP_TAILNET_VPS>:8081:80), api (<IP_TAILNET_VPS>:8080:3000), et postgres (127.0.0.1:5432:5432 — Postgres ne devrait même pas être sur le tailnet).

Ne committez pas ce changement dans le repo — il est spécifique au VPS. Soit utilisez un docker-compose.override.yml (gitignored sur le VPS), soit gardez-le comme un diff local. Le compose.yml du repo doit rester générique.

Alternative plus propre si vous ne voulez pas éditer le compose : laisser compose.yml tel quel et mettre une règle ufw sur le VPS qui autorise 8000/8080/8081 uniquement depuis 100.0.0.0/8 (la plage CGNAT Tailscale) :

bash
sudo ufw allow in on tailscale0
sudo ufw deny 8000
sudo ufw deny 8080
sudo ufw deny 8081
sudo ufw deny 5432
sudo ufw allow ssh
sudo ufw enable

Choisissez une approche (binding des ports ou firewall) — les deux marchent mais une seule suffit.

Étape 5 — Démarrer le stack ​

bash
cd ~/snag-tcg/tcg-collector-backend

# Build et démarrer postgres + api + nginx + tcg-vision (on saute embeddings — obsolète)
docker compose -f docker/compose.yml up -d --build postgres api nginx tcg-vision

# Suivre les logs jusqu'à ce que tout soit healthy (Ctrl+C quitte le tail, pas le stack)
docker compose -f docker/compose.yml logs -f

Temps de build attendu : ~5 à 15 min. Le plus long est tcg-vision (télécharge ~85 MB de poids DINOv2 et build PyTorch CPU).

Health checks depuis le laptop (toujours sur le tailnet) :

bash
curl http://<IP_TAILNET_VPS>:8000/health     # tcg-vision
curl http://<IP_TAILNET_VPS>:8080/sets       # api → postgres (vide tant que l'ingest n'a pas tourné)
curl -I http://<IP_TAILNET_VPS>:8081/        # nginx

Étape 6 — Premier ingest ​

Peuple Postgres avec sets/cartes et data/images/ avec les visuels. Long mais one-shot :

bash
docker compose -f docker/compose.yml run --rm ingest

Tail les logs dans un autre terminal si vous voulez suivre la progression. Comptez ~30 min selon INGEST_SETS et le rate-limit TCGdex.

Quand c'est fini, vérifier que les images sont bien sur disque :

bash
ls data/images/ | head
# A1  A1a  A2  ...
ls data/images/A1/001/
# high.webp  low.webp

Et que nginx les sert sur le tailnet :

bash
curl -I http://<IP_TAILNET_VPS>:8081/images/A1/001/high.webp
# HTTP/1.1 200 OK
# Content-Type: image/webp

Étape 7 — Mettre à jour l'app mobile ​

Sur votre laptop, dans le repo poc-tcg-collector :

bash
cd ~/projects/poc-tcg-collector   # ou là où il est sur votre machine
git checkout main && git pull
git checkout -b chore/repoint-scan-url-to-vps

Éditer src/scan/backendConfig.ts:45 :

ts
// Avant :
export const BACKEND_SCAN_URL = 'http://100.76.15.57:8000/scan-compat';
// Après (remplacez par l'IP tailnet de votre VPS) :
export const BACKEND_SCAN_URL = 'http://<IP_TAILNET_VPS>:8000/scan-compat';

Commit, push, ouvrir une PR (per règles du repo, pas de push direct sur main) :

bash
git add src/scan/backendConfig.ts
git commit -m "chore(scan): repoint BACKEND_SCAN_URL to VPS tailnet IP"
git push -u origin chore/repoint-scan-url-to-vps
gh pr create --base main

Après merge, rebuilder l'APK et réinstaller sur le téléphone :

bash
cd android
./gradlew assembleRelease
# Installer android/app/build/outputs/apk/release/app-release.apk sur le téléphone

Étape 8 — Test bout-en-bout depuis le téléphone ​

Le téléphone doit être sur Tailscale (c'est déjà le cas). Ouvrez l'app, scannez une carte.

Attendu :

  • Le scan revient en ~1-2 s.
  • Un visuel de carte apparaît (servi par votre nginx VPS, pas TCGdex).
  • Dans les logs tcg-vision côté VPS vous devriez voir un vrai POST /scan-compat, et juste après, une ligne nginx du type GET /images/A1/001/high.webp 200.

Si l'image reste blanche, vérifiez :

  • LOCAL_IMAGE_BASE_URL utilise bien l'IP tailnet du VPS (pas localhost ni 127.0.0.1).
  • La carte matchée par FAISS appartient à un set que vous avez ingéré. Sinon l'URL 404 — relancez l'ingest avec le set manquant dans INGEST_SETS.
  • tailscale status sur le téléphone — Android le met parfois en veille. Tapez la notification Tailscale pour confirmer que le tunnel est up.

Étape 9 — Décommissionner le stack laptop (optionnel mais recommandé) ​

Une fois que vous avez confirmé que le téléphone parle bien au VPS :

powershell
# Sur le laptop
cd C:\Users\Kael\Documents\Claude\Projects\SKN\tcg-collector-backend
docker compose -f docker/compose.yml down
# Containers stoppés et supprimés ; data/pg et data/images restent sur disque comme backup.

Les images Docker elles-mêmes peuvent rester (ça ne coûte rien tant qu'elles sont arrêtées) ; vous les supprimerez plus tard avec docker image prune -a une fois certain de ne pas vouloir rollback.

Maintenance récurrente ​

Les futures mises à jour côté VPS sont désormais en une ligne :

bash
cd ~/snag-tcg/tcg-collector-backend
git pull
docker compose -f docker/compose.yml up -d --build

Pour un ingest frais (nouveaux sets) : docker compose -f docker/compose.yml run --rm ingest.

Quand passer à l'Option A (domaine public + HTTPS) ​

À basculer quand l'une de ces conditions devient vraie :

  • Vous voulez que des utilisateurs hors Tailscale puissent scanner (vrais beta-testeurs, amis, public).
  • La connexion Tailscale du téléphone devient un point de friction (batterie, coupures sur mauvais réseau mobile).
  • Vous commencez à manipuler des données utilisateur que vous seriez mal à l'aise de transporter sur HTTP en clair, même sur un tailnet.

La migration est petite : installer Caddy sur le VPS, pointer un (sous-)domaine sur l'IP publique du VPS, swapper LOCAL_IMAGE_BASE_URL et BACKEND_SCAN_URL de http://<IP_TAILNET_VPS>:... vers https://scan.votre-domaine.com/.... Le stack compose lui-même ne change pas.