Aller au contenu
  1. Posts/

Perfect Homelab [A07] : sortir le stockage de Garage sur un dataset ZFS dédié

Fabien ALLAMANCHE
Auteur
Fabien ALLAMANCHE
Géomaticien @ Vienne Condrieu Agglomération
Sommaire
Perfect Homelab Annexe - Cet article fait partie d'une série.
Partie 7: Cet article

Perfect Homelab

Introduction
#

Dans l’annexe A02, nous avons déployé une instance Garage S3 sur la LXC 121 (storage) du nœud Morpheus, avec Docker Compose. À ce stade, les deux répertoires d’état de Garage — garage/meta (la base SQLite) et garage/data (les blocs d’objets) — sont des dossiers ordinaires sous /opt/docker/storage-stack/, donc sur le disque racine du conteneur.

Ce disque racine, c’est rpool/datastore/subvol-121-disk-0 : environ 17 Go, dont 13 encore libres. Suffisant pour des backups Proxmox modestes et quelques tests. Trop court dès que le stockage objet devient un vrai service — et c’est précisément ce qui arrive : à la suite de la série QFieldCloud auto-hébergé, un bucket de production doit atterrir sur cette instance. L’annexe A02 de QFieldCloud posait déjà un quota de 5 Go sur un nœud qui n’en déclarait que 4 : le genre de détail qui ne se voit qu’au moment où le disque se remplit.

Nous allons déplacer meta/ et data/ sur un dataset ZFS dédié de Morpheus, le monter dans la LXC 121 par un point de montage Proxmox, et brancher Garage dessus via un simple interrupteur dans le .env — sans toucher au sous-module ni au déploiement de l’A02, et de façon entièrement réversible.

Au passage, une corvée préalable : le dataset rpool/tank/matrix ne sert plus rien depuis la mise au placard de la stack Matrix. Il contient encore des données, il est encore monté dans la LXC — on le retire proprement avant de créer le nouveau.

Info : Rien ici ne modifie l’image, la configuration garage.toml ni le réseau de l’A02. On change uniquement vivent les octets. La bascule passe par une variable d’environnement : la retirer remet le stockage sur le disque racine, à l’identique.

Table des matières
#


Pourquoi un dataset dédié
#

Le disque racine d’un conteneur n’est pas fait pour porter un service de stockage. Un dataset ZFS séparé, si.

Trois raisons, dans l’ordre d’importance.

Le plafond. subvol-121-disk-0 fait 17 Go. On peut l’agrandir (pct resize 121 rootfs +50G), mais on empile alors backups Proxmox, base SQLite, blocs d’objets et journaux du conteneur sur le même volume — celui dont la saturation fige tout le conteneur d’un coup. Sortir data/ et meta/ sur leur propre dataset, c’est isoler le domaine de panne.

L’unité de sauvegarde. Un dataset est l’unité que ZFS sait snapshoter, envoyer et recevoir. zfs snapshot rpool/tank/garage@… fige meta et data au même instant : exactement ce dont l’annexe de sauvegarde a besoin pour un cliché cohérent. La politique Sanoid mise en place à l’annexe A01 couvre déjà rpool/tank en récursif — le nouveau dataset héritera de ses snapshots automatiques sans une ligne de configuration.

La lisibilité. zfs list -o space rpool/tank/garage répond en une ligne à « combien pèse Garage, snapshots compris ». Noyé dans le disque racine, ce chiffre n’existe pas.

Attention : rpool/tank vit sur le même pool, donc les mêmes disques que subvol-121-disk-0. On ne gagne pas des I/O plus rapides ni un support séparé — on gagne de la place, une frontière de snapshot et une cible de sauvegarde propre. Si vous cherchez de la performance ou de la résilience matérielle, c’est un autre chantier (pool dédié, vdev miroir supplémentaire).

La chaîne de montage, bout à bout
#

Cinq maillons séparent le dataset ZFS du répertoire que Garage ouvre en écriture. Chacun peut casser la chaîne ; autant les avoir sous les yeux.
graph TB
    subgraph MORPHEUS["Nœud Morpheus (PVE)"]
        SUBVOL[("rpool/datastore/subvol-121-disk-0
disque racine LXC · 17 Go")] DS[("rpool/tank/garage
dataset dédié · 215 Go dispo")] HOSTMP["/mnt/tank/garage (hôte)"] end subgraph CT121["LXC 121 · storage"] CTMP["/mnt/garage (conteneur)"] VOL["/var/lib/garage/{meta,data}"] GARAGE["conteneur garage"] end DS -->|"mountpoint hérité"| HOSTMP HOSTMP -->|"pct set 121 -mp0 …,mp=/mnt/garage"| CTMP CTMP -->|"bind Docker Compose"| VOL VOL -->|"data_dir / metadata_dir (garage.toml)"| GARAGE SUBVOL -.->|"avant : ./meta ./data
sur le disque racine"| GARAGE style DS fill:#c8e6c9,stroke:#2e7d32,stroke-width:2px,color:#000 style SUBVOL fill:#ffcdd2,stroke:#c62828,stroke-width:2px,color:#000 style GARAGE fill:#ffe0b2,stroke:#e65100,stroke-width:2px,color:#000
# Maillon Défini par
1 Dataset rpool/tank/garage Morpheus zfs create
2 Point de montage /mnt/tank/garage Morpheus propriété mountpoint (héritée de rpool/tank)
3 Point de montage /mnt/garage LXC 121 pct set 121 -mp0 …
4 Bind /var/lib/garage/{data,meta} conteneur garage volumes: du Compose (via .env)
5 data_dir / metadata_dir processus Garage garage.toml (inchangé)

Prérequis : retirer le dataset rpool/tank/matrix
#

Il ne sert plus, mais il n’est ni vide ni détaché. Trois vérifications, puis un retrait qui ne pardonne pas.

1. Inspecter avant de toucher
#

Sur Morpheus :

[root@morpheus:~] $ zfs list -o name,used,refer,mountpoint rpool/tank/matrix
NAME                USED  REFER  MOUNTPOINT
rpool/tank/matrix  52.8M  52.8M  /mnt/tank/matrix

# Des snapshots ? (Sanoid couvre rpool/tank en récursif — il y en a)
[root@morpheus:~] $ zfs list -t snapshot -o name,used rpool/tank/matrix
NAME                                                        USED
rpool/tank/matrix@autosnap_2026-09-08_00:00:00_daily        ...

# Qui l'ouvre encore ? (le point de montage de l'écran de config de la LXC)
[root@morpheus:~] $ grep -rn 'tank/matrix' /etc/pve/lxc/*.conf
/etc/pve/lxc/121.conf:mp0: /mnt/tank/matrix,mp=/mnt/matrix

Trois faits : 52 Mo de données bien réelles, des snapshots autosnap accrochés au dataset, et un point de montage mp0 encore déclaré sur la LXC 121. Aucun des trois ne se laisse ignorer.

2. Détacher le point de montage de la LXC
#

Tant que mp0 pointe dessus, le dataset est « busy » et la LXC embarque un montage mort. On retire l’entrée — c’est aussi le slot qu’on réutilisera pour Garage juste après :

[root@morpheus:~] $ pct set 121 --delete mp0
[root@morpheus:~] $ pct reboot 121

3. Mettre de côté, puis détruire
#

zfs destroy est immédiat et sans corbeille. À cette échelle (52 Mo), le plus sûr est de garder un filet quelques jours plutôt que de détruire séance tenante — soit un renommage hors de la vue, soit un send vers un fichier sur rpool/backups.

  • Renommer (repli rapide)
  • Archiver puis détruire
# Le dataset disparaît de /mnt/tank sans perdre un octet
[root@morpheus:~] $ zfs rename rpool/tank/matrix rpool/tank/_trash-matrix
[root@morpheus:~] $ zfs set mountpoint=none rpool/tank/_trash-matrix

# … quelques jours plus tard, une fois certain de ne rien regretter :
[root@morpheus:~] $ zfs destroy -r rpool/tank/_trash-matrix
# Cliché cohérent, envoyé compressé dans un fichier
[root@morpheus:~] $ zfs snapshot rpool/tank/matrix@adieu
[root@morpheus:~] $ zfs send rpool/tank/matrix@adieu | zstd \
    > /rpool/backups/matrix-adieu-$(date +%F).zfs.zst

# Vérifier que le fichier est lisible (liste le flux sans l'écrire)
[root@morpheus:~] $ zstd -dc /rpool/backups/matrix-adieu-*.zfs.zst | zfs receive -nv rpool/tank/verif-matrix

# Puis détruire la source, snapshots compris
[root@morpheus:~] $ zfs destroy -r rpool/tank/matrix
Erreur à éviter : zfs destroy rpool/tank/matrix sans -r échoue avec cannot destroy … filesystem has children — ce sont les snapshots autosnap de Sanoid. Le -r les emporte avec le dataset. N’utilisez jamais -R ici : il détruirait aussi d’éventuels clones dépendants, sans prévenir.
stateDiagram-v2
    [*] --> Actif : dataset en service
    Actif --> Détaché : pct set 121 --delete mp0
    Détaché --> MisÀLécart : zfs rename → _trash-matrix
ou zfs send → fichier MisÀLécart --> Détruit : zfs destroy -r Détruit --> [*]

Créer le dataset ZFS sur Morpheus
#

Un zfs create, quelques propriétés choisies pour un stockage d’objets, et le point de montage tombe tout seul au bon endroit.
[root@morpheus:~] $ zfs create rpool/tank/garage

# rpool/tank est monté sur /mnt/tank → l'enfant hérite de /mnt/tank/garage
[root@morpheus:~] $ zfs get -o property,value mountpoint rpool/tank/garage
PROPERTY    VALUE
mountpoint  /mnt/tank/garage

Trois propriétés méritent d’être posées d’emblée :

# Blocs alignés sur la taille de bloc par défaut de Garage (1 Mio)
[root@morpheus:~] $ zfs set recordsize=1M rpool/tank/garage

# Compression légère à abandon rapide : Garage compresse déjà ses objets
# (compression_level = 2 dans garage.toml), lz4 ne s'acharne pas sur
# de l'incompressible et coûte quasi rien sur le reste.
[root@morpheus:~] $ zfs set compression=lz4 rpool/tank/garage

# Pas d'intérêt à réécrire un atime à chaque lecture de bloc
[root@morpheus:~] $ zfs set atime=off rpool/tank/garage
Astuce : recordsize=1M est idéal pour data/ (gros blocs) mais peu flatteur pour meta/ (SQLite, petites écritures aléatoires). À l’échelle d’un homelab, meta/ pèse quelques dizaines de Mo et l’écart est invisible. S’il venait à grossir, créez un enfant dédié — zfs create -o recordsize=16K rpool/tank/garage/meta — qui apparaîtra sous le même point de montage sans rien changer au reste.

Sanoid (annexe A01) verra le nouveau dataset au prochain passage de son timer et commencera à le snapshoter selon le template data. Rien à faire.

Monter le dataset dans la LXC 121
#

Un point de montage Proxmox, un redémarrage du conteneur — et une histoire de décalage d’UID à régler pour les conteneurs non privilégiés.

1. Déclarer le point de montage
#

Le slot mp0 a été libéré en retirant Matrix. On le réutilise :

[root@morpheus:~] $ pct config 121 | grep -E '^mp[0-9]|unprivileged'
unprivileged: 1

[root@morpheus:~] $ pct set 121 -mp0 /mnt/tank/garage,mp=/mnt/garage,backup=0
[root@morpheus:~] $ pct reboot 121
Attention : backup=0 n’est pas cosmétique. Sans lui, vzdump (et donc PBS) tenterait d’avaler l’intégralité des objets Garage à chaque sauvegarde du conteneur — potentiellement des centaines de Go. Les objets se sauvegardent au niveau du dataset ZFS, pas du conteneur.

2. Régler les droits (conteneur non privilégié)
#

unprivileged: 1 : dans la LXC 121, root (UID 0) correspond à l’UID 100000 sur Morpheus, et l’utilisateur allfab (UID 1000) à l’UID 101000. Le dataset fraîchement créé appartient à root:root de l’hôte (UID 0) — vu depuis le conteneur, il apparaît en nobody:nogroup et n’est pas inscriptible.

Le conteneur garage tourne en root (l’image dxflrs/garage ne déclare pas d’utilisateur — docker exec garage id le confirme). Il faut donc donner le dataset au root du conteneur, c’est-à-dire à l’UID 100000 côté hôte :

# Sur Morpheus, AVANT de rebrancher Garage
[root@morpheus:~] $ chown -R 100000:100000 /mnt/tank/garage
Alternative : plutôt que de décaler la propriété du dataset, on peut décaler la vue du conteneur avec un lxc.idmap dans /etc/pve/lxc/121.conf (mapper l’UID 0 du conteneur sur l’UID 0 de l’hôte pour ce sous-arbre). Plus fin, mais plus verbeux, et à réserver au cas où le dataset est partagé avec d’autres conteneurs ayant des UID différents. Pour un dataset dédié à une seule LXC, le chown suffit.

Pour un conteneur privilégié (unprivileged: 0), pas de décalage : chown -R 0:0 /mnt/tank/garage (ou l’UID applicatif si Garage tournait sous un utilisateur dédié).

3. Vérifier depuis le conteneur
#

[allfab@storage:~] $ df -h /mnt/garage
Filesystem              Size  Used Avail Use% Mounted on
rpool/tank/garage       215G  128K  215G   1% /mnt/garage

[allfab@storage:~] $ sudo -u '#0' touch /mnt/garage/.rw-test && echo OK && sudo rm /mnt/garage/.rw-test
OK

Câbler Garage sur le dataset
#

Une variable dans le .env, une interpolation dans le Compose. Absente, le comportement de l’A02 est conservé au bit près.

1. L’interrupteur, dans .env
#

Le .env de la stack (/opt/docker/storage-stack/.env) reçoit une variable :

# /opt/docker/storage-stack/.env
COMPOSE_PROJECT_NAME=garage

# --- STOCKAGE ---
# Racine des répertoires meta/ et data/ de Garage.
# Vide ou absente  -> "." (dossier garage/), comportement A02, disque racine LXC.
# /mnt/garage      -> dataset ZFS rpool/tank/garage monté dans la LXC.
GARAGE_STORAGE_ROOT=/mnt/garage

# GARAGE
RPC_SECRET = "…"
ADMIN_TOKEN = "…"
METRICS_TOKEN = "…"
# … (inchangé)

2. L’interpolation, dans garage/docker-compose.yml
#

# /opt/docker/storage-stack/garage/docker-compose.yml
---
services:
  garage:
    container_name: garage
    image: dxflrs/garage:v2.3.0
    hostname: allfabox
    restart: unless-stopped
    ports:
      - 3900:3900
      - 3901:3901
      - 3902:3902
      - 3903:3903
    volumes:
      - ./config/garage.toml:/etc/garage.toml
      - ${GARAGE_STORAGE_ROOT:-.}/meta:/var/lib/garage/meta   # <- était ./meta
      - ${GARAGE_STORAGE_ROOT:-.}/data:/var/lib/garage/data   # <- était ./data
    networks:
      - storage
  # garage-webui : inchangé

${GARAGE_STORAGE_ROOT:-.} se lit « la valeur de GARAGE_STORAGE_ROOT, ou . si elle est vide ou absente ». Compose interpole depuis le .env du dossier courant. Le . se résout relativement à l’emplacement du fichier Compose (garage/) : le défaut reste donc garage/meta et garage/data, à l’identique de l’A02.

Info : ne lancez pas encore docker compose up. La configuration est prête, mais les données sont toujours dans garage/data et garage/meta. Passer à /mnt/garage maintenant démarrerait Garage sur un dataset vide — et le nœud se réamorcerait sous une nouvelle identité. La migration d’abord.

Migrer les données existantes
#

Les octets d’abord, la bascule ensuite. Jamais l’inverse — c’est la même discipline que pour le déménagement du stockage QFieldCloud.

1. Arrêter Garage
#

meta/ est une base SQLite : elle doit être au repos pendant la copie.

[allfab@storage:storage-stack] $ docker compose stop garage garage-webui

2. Recopier meta/ puis data/
#

Depuis la LXC 121, en root (les chemins source et destination traversent le décalage d’UID, d’où --numeric-ids) :

[root@storage:~] $ rsync -aHAX --numeric-ids --info=progress2 \
    /opt/docker/storage-stack/garage/meta/  /mnt/garage/meta/
[root@storage:~] $ rsync -aHAX --numeric-ids --info=progress2 \
    /opt/docker/storage-stack/garage/data/  /mnt/garage/data/

# Deuxième passe à blanc : elle ne doit rien avoir à transférer
[root@storage:~] $ rsync -naHAX --numeric-ids --itemize-changes \
    /opt/docker/storage-stack/garage/data/  /mnt/garage/data/

3. Rebrancher et vérifier l’identité du nœud
#

[allfab@storage:storage-stack] $ docker compose up -d

# Le nœud doit reparaître avec le MÊME ID qu'avant la migration
[allfab@storage:storage-stack] $ docker exec -ti garage /garage status
==== HEALTHY NODES ====
ID                Hostname  Address             Tags  Zone      Capacity   DataAvail
0f6796f7b25ec365  allfabox  10.10.20.21:3901          allfabox  4.0 GB     215 GB (99.9%)

DataAvail a bondi : Garage voit désormais l’espace du dataset. L’ID 0f6796f7b25ec365 est inchangé — c’est la preuve que meta/ a bien suivi. Un ID différent signifierait que seul data/ a été copié : dans ce cas, on arrête tout, on recopie meta/, on repart. Surtout, on n’applique aucun layout dans cet état.

# Contrôle de lecture rapide
[allfab@storage:storage-stack] $ docker exec -ti garage /garage bucket list
[allfab@storage:storage-stack] $ mc ls garage/

4. Garder l’ancien répertoire quelques jours
#

[allfab@storage:storage-stack] $ mv garage/data garage/data.old && mv garage/meta garage/meta.old

Il ne coûte que de la place, et il vaut cher le jour où quelque chose a été oublié. On le supprime une fois la nouvelle configuration éprouvée.

sequenceDiagram
    participant A as Admin (LXC 121)
    participant D as Docker Compose
    participant R as rsync
    participant G as Garage
    A->>D: docker compose stop garage garage-webui
    A->>R: rsync meta/ puis data/ → /mnt/garage
    R-->>A: 2ᵉ passe à blanc : 0 transfert
    A->>A: .env : GARAGE_STORAGE_ROOT=/mnt/garage
    A->>D: docker compose up -d
    D->>G: montage /mnt/garage/{meta,data}
    G-->>A: garage status → même ID de nœud
    A->>A: mv garage/data → data.old (repli)

Réviser la capacité du layout
#

Le dataset offre 215 Go là où le disque racine en promettait 13. La capacité déclarée du layout peut enfin refléter la réalité.

L’A02 l’expliquait en détail : la valeur -c d’un layout assign n’est ni un quota, ni une réservation — c’est une clé de répartition proportionnelle entre nœuds. Sur un nœud unique elle n’a aucun effet observable, mais la consigne tenait : déclarer une valeur honnête vis-à-vis du disque, pour qu’elle soit juste le jour où un second nœud arrive.

[allfab@storage:storage-stack] $ docker exec -ti garage /garage layout assign -c 200G 0f6796f7b25ec365
[allfab@storage:storage-stack] $ docker exec -ti garage /garage layout show
[allfab@storage:storage-stack] $ docker exec -ti garage /garage layout apply --version 3

Sans danger sur un nœud unique : il n’existe nulle part où déplacer des données. Le numéro de version (--version 3 ici) s’incrémente à chaque applylayout show donne le bon.

Accueillir le bucket QFieldCloud de production
#

C’était la raison d’être de ce déménagement. Le bucket de production peut désormais poser un quota qui tient debout.

À la suite de la série QFieldCloud, l’instance de production consommera ce Garage. On lui dédie un bucket et une clé, avec un quota qui — cette fois — se situe sous la capacité du nœud :

[allfab@storage:storage-stack] $ docker exec -ti garage /garage bucket create qfieldcloud-prod
[allfab@storage:storage-stack] $ docker exec -ti garage /garage key create qfieldcloud-prod-app
[allfab@storage:storage-stack] $ docker exec -ti garage /garage bucket allow \
    --read --write --owner qfieldcloud-prod --key qfieldcloud-prod-app
[allfab@storage:storage-stack] $ docker exec -ti garage /garage bucket set-quotas qfieldcloud-prod --max-size 100G

Côté QFieldCloud, rien à changer dans les octets : l’endpoint_url du bloc STORAGES reste http://10.10.20.21:3900. Rien, dans le stockage, ne mémorise l’adresse par laquelle on l’a atteint — le dataset sous-jacent est invisible pour le client S3.

Bucket Usage Quota
proxmox-backups Datastore S3 de PBS selon besoin
qfieldcloud Instance QFieldCloud de test (A02 QFC) 5 G
qfieldcloud-prod Instance QFieldCloud de production 100 G
mon-site Site statique via S3 Web

Tous partagent le même dataset rpool/tank/garage : un seul snapshot ZFS les fige tous ensemble.

QFieldCloud [A02] — sortir le stockage objet de la machine

Troubleshooting
#

flowchart TD
    START["Garage ne démarre pas
ou /mnt/garage est vide"] --> Q1{"/mnt/garage visible
dans la LXC ?"} Q1 -->|Non| F1["pct config 121 : mp0 présent ?
pct reboot 121"] Q1 -->|Oui, mais nobody:nogroup| F2["chown -R 100000:100000
/mnt/tank/garage (sur l'hôte)"] Q1 -->|Oui, accessible| Q2{"garage status :
même ID de nœud ?"} Q2 -->|Non, ID nouveau| F3["meta/ non migré →
stop, rsync meta/, up
NE PAS apply de layout"] Q2 -->|Oui| Q3{"Erreurs
Permission denied
os error 13 ?"} Q3 -->|Oui| F2 Q3 -->|Non| OK["Chaîne OK"] style F2 fill:#ffe0b2,stroke:#e65100,stroke-width:2px,color:#000 style F3 fill:#ffcdd2,stroke:#c62828,stroke-width:2px,color:#000 style OK fill:#c8e6c9,stroke:#2e7d32,stroke-width:2px,color:#000

zfs destroy : « filesystem has children » ou « dataset is busy »
#

Erreur : cannot destroy 'rpool/tank/matrix': filesystem has children → snapshots Sanoid accrochés, ajoutez -r. cannot unmount '/mnt/tank/matrix': pool or dataset is busy → un point de montage mpX d’une LXC pointe encore dessus (grep -rn 'tank/matrix' /etc/pve/lxc/*.conf, puis pct set <id> --delete mpX).

Garage démarre avec un nœud « inconnu »
#

Symptôme : garage status liste un ID différent, bucket list est vide. Cause : le dataset a été branché avec data/ mais sans meta/ — Garage a créé un nœud neuf. Solution : docker compose stop garage, rsync -aHAX --numeric-ids /opt/docker/storage-stack/garage/meta.old/ /mnt/garage/meta/, docker compose up -d. Vérifier le retour de l’ID d’origine avant toute commande layout.

Permission denied (os error 13) sur /var/lib/garage/data
#

Le décalage d’UID n’a pas été appliqué, ou l’a été après le premier démarrage. Sur Morpheus : chown -R 100000:100000 /mnt/tank/garage (conteneur non privilégié), puis docker compose restart garage.

La sauvegarde du conteneur 121 explose en taille
#

Le point de montage a été ajouté sans backup=0. Corriger : pct set 121 -mp0 /mnt/tank/garage,mp=/mnt/garage,backup=0.

Vérifications
#

# --- Sur Morpheus ---
[root@morpheus:~] $ zfs list -o name,used,avail,mountpoint,compression,recordsize rpool/tank/garage
[root@morpheus:~] $ zfs list -t snapshot rpool/tank/garage        # Sanoid a pris le relais
[root@morpheus:~] $ pct config 121 | grep ^mp0
mp0: /mnt/tank/garage,mp=/mnt/garage,backup=0

# --- Dans la LXC 121 ---
[allfab@storage:storage-stack] $ df -h /mnt/garage
[allfab@storage:storage-stack] $ docker inspect garage -f '{{ range .Mounts }}{{ .Source }} -> {{ .Destination }}{{ "\n" }}{{ end }}'
/mnt/garage/meta -> /var/lib/garage/meta
/mnt/garage/data -> /var/lib/garage/data

# --- Garage ---
[allfab@storage:storage-stack] $ docker exec -ti garage /garage status      # ID de nœud inchangé
[allfab@storage:storage-stack] $ docker exec -ti garage /garage bucket list  # buckets intacts

Test de bout en bout — écrire et relire un objet :

[allfab@storage:storage-stack] $ echo "dataset ok $(date)" > /tmp/probe.txt
[allfab@storage:storage-stack] $ mc cp /tmp/probe.txt garage/qfieldcloud-prod/
[allfab@storage:storage-stack] $ mc cat garage/qfieldcloud-prod/probe.txt
[allfab@storage:storage-stack] $ mc rm garage/qfieldcloud-prod/probe.txt

Conclusion
#

Garage écrit désormais sur un dataset ZFS dédié, taillé pour grandir et pour être sauvegardé proprement — sans qu’une ligne de garage.toml ait bougé.

Ce que vous avez mis en place :

  • Un ancien dataset (rpool/tank/matrix) retiré proprement : détaché de la LXC, mis à l’écart, puis détruit avec -r.
  • Un dataset rpool/tank/garage avec recordsize=1M, compression=lz4, atime=off, monté dans la LXC 121 par un point de montage Proxmox backup=0.
  • Le décalage d’UID des conteneurs non privilégiés réglé côté hôte.
  • Une bascule opt-in dans le .env (GARAGE_STORAGE_ROOT) : la retirer restaure le comportement de l’A02.
  • Une migration rsync qui préserve l’ID de nœud Garage — meta d’abord, data ensuite, contrôle avant tout layout.
  • Une capacité de layout révisée à 200 Go et un bucket qfieldcloud-prod dont le quota tient enfin sous celle du nœud.
Astuce : la sauvegarde de Garage devient triviale — zfs snapshot rpool/tank/garage@… fige meta et data au même instant, et zfs send l’expédie hors-site en incrémental. Et le jour où un second nœud rejoint le cluster, la capacité -c déclarée ici cesse d’être décorative : elle décide de la part des données qui atterrit sur chaque nœud.

Ressources
#

Perfect Homelab Annexe - Cet article fait partie d'une série.
Partie 7: Cet article

Articles connexes