Pourquoi ne pas laisser MinIO là où il est
Le dépôt minio/minio a été archivé par son propriétaire le 25 avril 2026 : il est en lecture seule, son README annonce que le projet n'est plus maintenu et précise que les binaires déjà publiés ne recevront plus de mises à jour. Concrètement, la prochaine faille de votre serveur S3 ne sera corrigée par personne en amont. Le client mc l'a été le 14 juillet 2026.
Votre MinIO continue de fonctionner : rien ne casse le jour de l'archivage. Mais un stockage objet est exposé et tourne désormais sans correctif de sécurité. Deux cibles auto-hébergeables reviennent dans les discussions : Garage, publié sous AGPLv3, et RustFS, publié sous licence Apache 2.0.
L'installation de chacune a son guide : Garage sur un VPS et RustFS sur un VPS. Ici, on part d'un MinIO en production : déplacer les données, prouver que rien ne manque, basculer sans URL cassée.
Ce qu'une copie de bucket ne déplace pas toute seule
- Les objets courants — c'est la partie facile :
rcloneoumc mirrorles copient, avec un contrôle à faire ensuite. - Les anciennes versions — la documentation de
mc mirrorprécise qu'il ne synchronise que l'objet courant, sans information de version. - Les utilisateurs et les politiques IAM — ils vivent dans MinIO, pas dans les buckets. Les secrets ne s'exportent pas : on régénère des clés côté cible.
- Les URLs présignées déjà distribuées — la signature dépend de l'hôte, de la région et des clés. Un changement d'endpoint les invalide.
- Les écarts de compatibilité S3 — ACL, politiques de bucket, accès anonyme, versioning : chaque cible a ses trous, à lire dans sa documentation plutôt qu'à supposer.
- La configuration des clients — endpoint, région, adressage path-style : tout ce que votre application, vos sauvegardes
resticou votre CDN ont en dur.
Prérequis et plan de capacité
Il vous faut un accès administrateur à MinIO, un poste ou un VPS avec mc et rclone, et une cible déjà installée et joignable en HTTPS (voir les deux guides ci-dessus pour le reverse proxy et le certificat). La règle de capacité est simple : tant que vous n'avez pas validé la migration, vous gardez la source intacte, donc vous payez le disque deux fois.
Notre recommandation : prévoir sur la cible la taille des données de la source plus une marge d'environ 30 %. Sur le même VPS, cela fait au moins deux fois le volume actuel plus la marge ; sur deux VPS, chacun porte sa part. Un nœud Garage unique avec replication_factor = 1 ne garde qu'une copie de chaque objet : ce n'est pas une sauvegarde, et la source encore en place est votre seul filet pendant la migration.
Étape 1 : inventorier la source avant de copier
Déclarer MinIO comme alias
Créez un alias
srcavec des identifiants qui lisent tous les buckets :mc alias set src https://s3.mondomaine.com ACCESS_KEY SECRET_KEYCompter les objets et la taille
Pour chaque bucket, notez le nombre d'objets et le volume :
mc du --recursive src/mediasmc ls --recursive src/medias | wc -l
Reportez ces chiffres dans un tableau « avant / après » : c'est votre preuve de fin de migration.Compter les versions
Si le versioning est actif, la liste des versions est plus longue que celle des objets :
mc ls --recursive --versions src/medias | wc -l
La documentation demc lsprécise que--versionsexige un service qui gère le versioning. Un écart important entre les deux comptes dit combien d'historique est en jeu.Exporter la liste des politiques et des utilisateurs
Gardez une trace de ce qu'il faudra recréer :
mc admin policy list srcmc admin user list src
Les secrets ne sont pas dans cette sortie et ne peuvent pas l'être : vous les régénérerez.Relever la configuration des clients
Dans chaque application qui parle à MinIO, notez l'endpoint, la région, le style d'adressage et la variable d'environnement qui les porte : la bascule ne changera que cela.
Garage et RustFS : les écarts à connaître avant de choisir
Faites défiler le tableau
| Sujet | Garage | RustFS |
|---|---|---|
| Licence | AGPLv3 (README du dépôt) | Apache 2.0 (README du dépôt) |
| Versioning | Non géré : `PutBucketVersioning` et `ListObjectVersions` figurent comme manquants | Indiqué comme disponible dans le README |
| Politiques de bucket et ACL | Aucune des deux : un système propre, par clé et par bucket | IAM et politiques de bucket ; les ACL sont volontairement non gérées |
| Accès anonyme | Refusé, avec une réponse 403 | Évalué d'après la politique du bucket |
| Région du client | `s3_region` de `garage.toml`, à reporter dans le client | `RUSTFS_REGION`, `us-east-1` par défaut, à reporter dans le client |
| Adressage | Path-style et vhost-style gérés | Path-style par défaut, sans configuration DNS |
| URLs présignées | Gérées | Gérées, l'hôte, le schéma, le port, la région et le chemin doivent correspondre à la signature |
| API d'administration MinIO | Sans objet : on administre avec la commande `garage` | Surface séparée : le S3 fonctionne sans que `mc admin` soit garanti |
Versioning : ce que la copie perd, et que faire
C'est le point qui décide du choix de la cible. Garage ne gère pas le versioning : sa page de compatibilité S3 le dit sans détour. Si vos buckets conservent un historique que l'application consulte ou que vous devez garder pour des raisons métier, Garage n'en est pas la destination. RustFS indique le versioning comme disponible, mais la copie par rclone sync ou mc mirror n'y recréera pas votre historique.
Trois voies, à choisir bucket par bucket. Assumer la perte : seule la version courante passe, ce qui convient quand le versioning ne servait que de filet contre l'erreur humaine. Copier version par version : vous listez les identifiants avec mc ls --versions, puis vous rejouez les versions de la plus ancienne à la plus récente vers une cible qui gère le versioning, après l'avoir activé sur le bucket ; les dates des versions seront celles de la copie, pas d'origine. Ou archiver l'historique à part, dans un bucket d'archive, et ne migrer que le courant. Dans tous les cas, testez d'abord sur un objet à plusieurs versions.
Étape 2 : préparer la cible, Garage ou RustFS
Garage : assigner puis appliquer le layout
Aucun bucket n'est utilisable tant que le layout n'est pas appliqué. Relevez l'identifiant du nœud, puis assignez-lui une zone et une capacité :
garage statusgarage layout assign -z dc1 -c 1G <node_id>garage layout apply --version 1
Ajustez-cà l'espace disque réellement alloué.Garage : créer la clé, le bucket et les droits
Les droits se donnent par clé et par bucket :
garage key create app-keygarage bucket create mediasgarage bucket allow --read --write --owner medias --key app-key
Le secret s'affiche à la création de la clé : copiez-le tout de suite dans votre gestionnaire de secrets.Garage : reporter la région dans le client
La section
[s3_api]degarage.tomldéfinits3_region. Mettez exactement cette valeur dans la configuration de vos clients, et gardez l'adressage path-style. Une région différente donne des signatures refusées.RustFS : alias et bucket
Avec
mcconfiguré sur la cible :mc alias set dst http://127.0.0.1:9000 ACCESS_KEY SECRET_KEYmc mb dst/medias
La documentation RustFS recommande des utilisateurs IAM ou des comptes de service plutôt que les clés racine pour les applications.RustFS : recréer utilisateurs et politiques
Reprenez la liste exportée à l'étape 1 et recréez chaque utilisateur et chaque politique côté RustFS, avec une clé neuve par application. Alignez ensuite la région :
RUSTFS_REGIONvautus-east-1par défaut, et le client doit utiliser la même.
Étape 3 : copier avec rclone et prouver le résultat
Déclarer les deux remotes
Utilisez le fournisseur
Miniopour la source etOtherpour la cible :rclone config create src s3 provider=Minio access_key_id=ACCESS_KEY secret_access_key=SECRET_KEY endpoint=https://s3.mondomaine.comrclone config create dst s3 provider=Other access_key_id=ACCESS_KEY secret_access_key=SECRET_KEY endpoint=http://127.0.0.1:3900 region=garageregion=garagesupposes3_region = "garage", la valeur de l'exemple de Garage : mettez la vôtre si elle diffère. Pour RustFS, remplacez l'endpoint par celui de la cible et la région par la valeur deRUSTFS_REGION.Faire un essai à blanc
rclone syncmodifie la destination pour qu'elle reflète la source, suppressions comprises. Ne l'inversez jamais, et jouez d'abord l'essai :rclone sync src:medias dst:medias --checksum --metadata --transfers 16 --dry-runLancer la copie à chaud
MinIO reste en service pendant ce premier passage :
rclone sync src:medias dst:medias --checksum --metadata --transfers 16 --progress--metadatademande à rclone de préserver les métadonnées : contrôlez sur un objet témoin que le type de contenu est arrivé intact.Vérifier la copie
Contrôlez que tout ce qui est dans la source existe dans la cible :
rclone check src:medias dst:medias --one-way --combined /tmp/check-medias.txt
Le fichier liste chaque chemin avec un symbole :=identique,+absent de la destination,*différent,!erreur. Zéro ligne+,*ou!est l'objectif.Recompter après la copie
Rejouez
mc du --recursiveet le décompte d'objets sur la cible, avec l'aliasdst, et remplissez la colonne « après » de votre tableau. Les deux chiffres doivent être identiques, sauf écarts expliqués par les versions.
ETag multipart et mc mirror : lire les écarts sans se tromper
L'ETag d'un petit objet envoyé en une seule requête est son MD5, et rclone s'en sert comme somme de contrôle. Pour un objet multipart, la documentation rclone rappelle que l'ETag n'est plus le MD5 des données : c'est le MD5 de la suite des MD5 de chaque partie, suivi du nombre de parties. Il dépend donc de la taille des parties, que le client a choisie à l'envoi.
Votre MinIO a reçu l'objet avec un découpage, rclone le renvoie avec un autre : contenu identique, ETag différent. Un rclone check qui signale ces objets comme différents n'a pas forcément raison. Rejouez-le sur eux avec --download, qui télécharge les deux côtés et compare les données elles-mêmes : plus lent, mais sans ambiguïté. Même vigilance avec mc mirror : sa documentation précise que des sommes composites peuvent différer simplement parce que la répartition en parties a changé.
Si vous préférez rester dans l'écosystème mc, la copie s'écrit avec mc mirror --preserve src/medias dst/medias. L'option --preserve conserve les attributs de la source et ses règles de politique de bucket ; ce dernier point n'a de sens que si la cible sait les lire, ce qui exclut Garage.
Trois limites à connaître. Sans --overwrite, un objet déjà présent sur la cible n'est pas recopié : l'erreur est journalisée et la copie continue, ce qui rend la relance à moitié silencieuse. Avec --remove, les objets de la cible absents de la source sont supprimés, geste à réserver à un bucket que vous maîtrisez. Enfin, mc mirror ne synchronise que l'objet courant, sans version. Sa comparaison porte sur le nom, la taille, la date et la somme de contrôle, sans lire le contenu. Le dépôt de mc est lui aussi archivé : l'outil marche, mais ne bougera plus.
URLs présignées : pourquoi elles tombent à la bascule
Une URL présignée contient une signature calculée avec la clé secrète, l'hôte, la région et le chemin. La documentation RustFS le dit : l'hôte, le schéma, le port, la région et le chemin de la requête doivent correspondre à ceux utilisés pour signer. Changer d'endpoint ou de clé invalide donc tous les liens déjà émis, sans exception, et rien ne les redirige.
C'est une affaire de séquencement. Les liens générés à la volée par votre application se régénèrent dès qu'elle est reconnectée à la cible. Ceux envoyés par e-mail ou stockés en base mourront à la bascule : raccourcissez leur durée de vie quelques jours avant, et prévenez les équipes qui en distribuent à la main.
Étape 4 : basculer en trois temps, sans coupure franche
Sync initial à chaud
C'est la copie de l'étape 3, jouée pendant que MinIO sert encore le trafic. Elle dure le temps qu'il faut et ne gêne personne. Notez l'heure de fin.
Geler les écritures
Passez l'application en lecture seule ou en maintenance courte, ou activez une double écriture si votre code la permet. Les lectures peuvent continuer sur MinIO pendant que le sync final tourne.
Sync final avec --update
Ne transférez que ce qui a changé depuis :
rclone sync src:medias dst:medias --update --metadata --transfers 16 --progressrclone check src:medias dst:medias --one-way
Recomptez les objets et la taille, et comparez au tableau de l'étape 1.Changer l'endpoint par variable d'environnement
Votre application doit lire l'endpoint, la région et les clés dans son environnement, jamais dans le code. Modifiez ces variables, redémarrez le service, puis testez un envoi, une lecture et une suppression sur un objet témoin. Régénérez les liens présignés qui comptent.
Surveiller, puis seulement ensuite démonter
Laissez MinIO en place, arrêté ou en lecture seule, le temps d'un cycle complet de vos sauvegardes et tâches planifiées. Pointez de nouveau vos sauvegardes
resticvers la cible, voir le guide sur les sauvegardes restic.
Le retour arrière tient en une variable
Tant que MinIO n'est pas supprimé et que le dernier sync a été fait en lecture seule, revenir en arrière consiste à remettre l'ancien endpoint dans l'environnement et à redémarrer. Si des écritures ont eu lieu sur la cible depuis la bascule, rapatriez-les d'abord vers la source avec une copie rclone copy dans l'autre sens (jamais un sync, qui supprimerait), puis revenez. N'effacez MinIO qu'après une période d'observation que vous aurez fixée à l'avance.
Dépannage : les symptômes qui reviennent
Un appel sans signature sur Garage répond 403 avec le message Garage does not support anonymous access yet. C'est le comportement attendu de Garage, et la demande d'un accès anonyme figure toujours au suivi du projet comme une fonction jamais priorisée. Si votre application servait des fichiers publics, il faudra des URLs présignées ou un proxy devant le bucket.
Si les écritures sont refusées pour signature invalide après la bascule, comparez la région du client à celle de la cible (s3_region pour Garage, RUSTFS_REGION pour RustFS) et vérifiez que le client utilise l'adressage path-style. Si rclone check signale des objets multipart comme différents alors que leurs tailles sont égales, rejouez-le avec --download. Si une relance de mc mirror ne recopie pas un objet modifié, c'est l'absence de --overwrite. Enfin, si un bucket est vide côté cible alors que le sync a réussi, vérifiez que la clé a bien reçu --read --write sur ce bucket côté Garage ou la bonne politique côté RustFS.
La documentation officielle
Relisez ces sources avant d'agir, ces projets évoluent : le dépôt github.com/minio/minio pour le statut et la licence de MinIO ; github.com/rustfs/rustfs et docs.rustfs.com pour RustFS ; garagehq.deuxfleurs.fr pour la compatibilité S3 de Garage ; rclone.org pour sync et check ; la documentation MinIO de mc mirror. Pour sauvegarder ce stockage, voyez l'article 3-2-1 avec restic et S3.