Étapes techniques pour quitter Firebase et passer à un hébergement autonome à 5 $ par mois
TuBrief 편집팀
2026년 7월 14일
0
Computing/Software원본 영상을 바탕으로 AI의 도움을 받아 작성했습니다. 원본 영상이 기준입니다.
커뮤니티의 다른 글
댓글 (0)
Log in to leave a comment
아직 작성된 글이 없습니다
원본 영상을 바탕으로 AI의 도움을 받아 작성했습니다. 원본 영상이 기준입니다.
Log in to leave a comment
아직 작성된 글이 없습니다
Firebase est séduisant au début. On peut se concentrer uniquement sur les fonctionnalités sans se soucier de l'infrastructure. Cependant, dès que le service connaît une petite croissance, les chiffres sur la facture commencent à grimper. Pourtant, l'idée de passer à un hébergement autonome fait peur. Que faire si les données sont perdues ? Que faire si le service s'interrompt à chaque déploiement ?
C'est une inquiétude tout à fait légitime. Mais il existe une solution. J'ai résumé ici les étapes concrètes pour migrer vos données Firestore vers PocketBase, un backend tout-en-un basé sur SQLite, et créer un environnement de déploiement sans interruption (zéro downtime) en utilisant GitHub Actions et Nginx.
Le premier obstacle lors du transfert de données non structurées de Firestore vers le schéma relationnel de PocketBase est la longueur des identifiants. Firestore utilise des ID de document de 20 caractères, tandis que PocketBase impose par défaut une limite de 15 caractères alphanumériques. Si vous forcez l'insertion sans tenir compte de cette longueur, vous rencontrerez l'erreur validation_length_invalid.
Pour éviter ce problème, il faut réduire la longueur tout en préservant l'unicité. La méthode la plus fiable consiste à hacher l'ID Firestore avec SHA256, puis à ne conserver que les 15 premiers caractères. Vous n'avez pas à vous soucier des collisions. Même sur de grands ensembles de données, la probabilité que des hashs de 15 caractères se chevauchent est négligeable.
Voici la structure du script de migration Node.js pour gérer cela. Il utilise les paquets firebase-admin et axios pour récupérer les données par lots de 100.
`javascript
// migration.js
const admin = require('firebase-admin');
const axios = require('axios');
const crypto = require('crypto');
admin.initializeApp({
credential: admin.credential.applicationDefault()
});
const db = admin.firestore();
const PB_URL = 'http://127.0.0.1:8090/api/collections/posts/records';
async function migrate() {
let lastDoc = null;
let hasMore = true;
while (hasMore) {
let query = db.collection('posts').orderBy('name').limit(100);
if (lastDoc) {
query = query.startAfter(lastDoc);
}
const snapshot = await query.get();
if (snapshot.empty) {
hasMore = false;
break;
}
for (const doc of snapshot.docs) {
const data = doc.data();
// Conversion de l'ID Firestore de 20 caractères en 15 caractères alphanumériques
const newId = crypto.createHash('sha256').update(doc.id).digest('hex').substring(0, 15);
try {
await axios.post(PB_URL, {
id: newId,
firestore_id: doc.id, // Enregistrement de l'ID d'origine pour vérification éventuelle
title: data.title,
content: data.content
});
} catch (err) {
console.error(`Échec de transfert : ${doc.id}`, err.response?.data);
}
}
lastDoc = snapshot.docs[snapshot.docs.length - 1];
}
}
migrate();
`
Pour effectuer cette opération sans interruption, il faut prévoir une période de transition. Commencez par migrer plus de 90 % des données existantes en arrière-plan avec ce script. Ensuite, déployez un code de « double écriture » (Dual-Write) qui enregistre les données simultanément sur Firebase et PocketBase lors des opérations d'écriture dans votre application cliente. Après avoir vérifié que les données correspondent des deux côtés, passez les endpoints de lecture sur PocketBase et supprimez le code lié à Firebase. Pour l'utilisateur, le serveur ne sera jamais indisponible, pas même une seconde.
PocketBase est un backend léger qui s'exécute sous forme d'un binaire unique. Cependant, si vous vous contentez de le lancer sur un serveur Linux, vous n'aurez aucun moyen de récupérer le service s'il s'arrête à cause d'une erreur de mémoire insuffisante (OOM) imprévue.
Vous devez configurer systemd pour que le processus redémarre automatiquement s'il tombe. Créez un fichier /etc/systemd/system/pocketbase.service et enregistrez le contenu suivant :
`ini
[Unit]
Description=PocketBase Service
After=network.target
[Service]
Type=simple
User=pocketbase
Group=pocketbase
LimitNOFILE=65535
ExecStart=/opt/pocketbase/pocketbase serve --http="127.0.0.1:8090"
Restart=always
RestartSec=3
[Install]
WantedBy=multi-user.target
`
Ici, le réglage LimitNOFILE=65535 est crucial. Lors de l'utilisation de la fonctionnalité d'abonnement en temps réel (WebSockets) qui fait la force de PocketBase, cela empêche les déconnexions dues à la limite de descripteurs de fichiers par défaut de Linux lorsque de nombreux utilisateurs se connectent simultanément.
En amont, placez Nginx pour gérer les certificats SSL et le proxy. Vous devez ajouter des options dans la configuration /etc/nginx/sites-available/pocketbase pour éviter que les connexions de streaming en temps réel ne soient retardées.
`nginx
upstream pocketbase {
server 127.0.0.1:8090;
}
server {
server_name api.yourdomain.com;
location / {
proxy_pass http://pocketbase;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# Support du streaming en temps réel via WebSockets et SSE
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_buffering off;
}
}
`
Il ne reste plus qu'à appliquer le certificat HTTPS avec la commande certbot --nginx, et l'infrastructure de base est prête.
Selon mon expérience, même sur les VPS les plus économiques avec 512 Mo de RAM, en ajustant légèrement les réglages SQLite, vous pouvez facilement gérer plus de 1 000 requêtes légères par seconde. Pour maximiser les performances de SQLite lors de l'exécution de PocketBase, vous devez activer le mode WAL (Write-Ahead Log). PocketBase active le mode WAL par défaut, mais si vous devez manipuler SQLite directement, vérifiez les paramètres PRAGMA journal_mode=WAL; et PRAGMA synchronous=NORMAL;. Le goulot d'étranglement dû à l'attente d'écriture sur le disque disparaît presque totalement.
L'erreur la plus fréquente des développeurs indépendants est de copier directement le fichier de base de données SQLite (data.db) pendant que le serveur est actif. Cette méthode présente un risque élevé de corruption des données pendant la sauvegarde.
Nous utilisons Litestream. C'est un outil qui intercepte les frames WAL de SQLite en temps réel et envoie uniquement les parties modifiées vers un stockage cloud. Je recommande Cloudflare R2 comme dépôt de sauvegarde. Comme les frais de transfert sortant sont gratuits, cela ne coûte pratiquement rien, quelle que soit la quantité de sauvegardes effectuées.
Créez le fichier de configuration /etc/litestream.yml comme suit :
`yaml
dbs:
`
Avec cette configuration, même si le fichier db original est perdu, vous pouvez le restaurer jusqu'à l'état de la dernière seconde avec une seule commande :
bash litestream restore -if-replica-exists /opt/pocketbase/pb_data/data.db
Si vous avez accidentellement supprimé des données pendant le développement et que vous souhaitez restaurer à un moment précis, il est également possible de spécifier une valeur temporelle.
`bash
sudo systemctl stop pocketbase.service
litestream restore -timestamp "2026-07-14T15:00:00Z" -o /tmp/recovered.db /opt/pocketbase/pb_data/data.db
sqlite3 /tmp/recovered.db "PRAGMA integrity_check;"
mv /tmp/recovered.db /opt/pocketbase/pb_data/data.db
chown -R pocketbase:pocketbase /opt/pocketbase/pb_data/
sudo systemctl start pocketbase.service
`
Cloudflare R2 offre 10 Go de stockage gratuit par mois. Comme ils offrent également 1 million de requêtes d'écriture et 10 millions de requêtes de lecture gratuitement, les coûts de sauvegarde pour un service individuel tendent vers zéro.
Déployer avec Docker est pratique, mais sur un petit VPS avec 512 Mo ou 1 Go de RAM, la mémoire consommée par le démon Docker lui-même est précieuse. Implémentons un déploiement sans interruption (blue-green) en alternant les ports via systemd Linux, sans utiliser Docker.
Nous profitons de la caractéristique de SQLite qui permet à plusieurs processus de lire et d'écrire dans un même fichier. Il suffit d'enregistrer des modèles de service systemd pour pouvoir lancer le binaire PocketBase sur les ports 9011 (bleu) et 9012 (vert) respectivement.
Voici d'abord un exemple de workflow GitHub Actions pour transférer le binaire léger construit vers le serveur.
`yaml
name: Deploy
on:
push:
branches: [ main ]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: '1.22'
- name: Build
run: |
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-s -w" -o pocketbase main.go
- name: Transfer Binary to VPS
uses: appleboy/scp-action@master
with:
host: ${{ secrets.VPS_HOST }}
username: ${{ secrets.VPS_USER }}
key: ${{ secrets.VPS_KEY }}
source: "pocketbase"
target: "/srv/pocketbase/next_release"
`
Une fois que le binaire arrive sur le serveur, le script de déploiement s'exécute. Il vérifie quel port est actuellement actif (9011 ou 9012) et lance la nouvelle version du binaire sur le port inutilisé.
`bash
#!/bin/bash
CURRENT_PORT=$(curl -s http://127.0.0.1:8090/api/health | jq -r '.port' 2>/dev/null || echo "9011")
if [ "$CURRENT_PORT" = "9011" ]; then
TARGET_PORT="9012"
else
TARGET_PORT="9011"
fi
sudo systemctl start pocketbase@$TARGET_PORT.service
sleep 3
HEALTH_CHECK=TARGET_PORT/api/health | jq -r '.status')
if [ "HEALTH_CHECK" = "OK" ]; then
# Modification de la configuration upstream Nginx vers le nouveau port puis rechargement
echo "upstream pocketbase { server 127.0.0.1:TARGET_PORT; }" | sudo tee /etc/nginx/conf.d/upstream.conf
sudo systemctl reload nginx
# Arrêt du processus de l'ancienne version
sudo systemctl stop pocketbase@$CURRENT_PORT.service
echo "Déploiement terminé. Basculement vers le port TARGET_PORT.service
exit 1
fi
`
Grâce à cette méthode, un déploiement sans interruption fonctionne parfaitement sans outils d'orchestration de conteneurs coûteux. Même si un utilisateur envoie une requête pendant le déploiement, Nginx met en attente les requêtes durant l'instant du rechargement (en millisecondes) et les redirige ensuite en toute sécurité vers le nouveau port.
L'hébergement autonome demande un peu d'efforts lors de la première configuration, mais une fois en place, il devient une excellente base pour se concentrer entièrement sur le produit, sans se soucier des frais d'infrastructure facturés chaque mois.