6.0 KiB
06 — Stockage objet S3 (Hetzner Object Storage)
Migration MinIO → Hetzner Object Storage
Bonne nouvelle : aucune modification de code nécessaire.
Le code Xpeditis utilise déjà le AWS SDK v3 avec forcePathStyle: true et un endpoint configurable dans apps/backend/src/infrastructure/storage/s3-storage.adapter.ts :
// Ce code existant fonctionne avec Hetzner Object Storage
this.s3Client = new S3Client({
region,
endpoint, // ← Changer vers Hetzner
credentials: { accessKeyId, secretAccessKey },
forcePathStyle: !!endpoint, // ← true pour Hetzner (path-style S3)
});
Il suffit de changer 4 variables d'environnement :
# AVANT (MinIO local)
AWS_S3_ENDPOINT=http://localhost:9000
AWS_ACCESS_KEY_ID=minioadmin
AWS_SECRET_ACCESS_KEY=minioadmin
AWS_REGION=us-east-1
# APRÈS (Hetzner Object Storage)
AWS_S3_ENDPOINT=https://fsn1.your-objectstorage.com
AWS_ACCESS_KEY_ID=<hetzner_access_key>
AWS_SECRET_ACCESS_KEY=<hetzner_secret_key>
AWS_REGION=eu-central-1
AWS_S3_BUCKET=xpeditis-prod
Configuration détaillée
Variables d'environnement backend (.env.production)
# S3 / Hetzner Object Storage
AWS_S3_ENDPOINT=https://fsn1.your-objectstorage.com
AWS_ACCESS_KEY_ID=<votre_hetzner_access_key>
AWS_SECRET_ACCESS_KEY=<votre_hetzner_secret_key>
AWS_REGION=eu-central-1
AWS_S3_BUCKET=xpeditis-prod
Endpoint par région :
- Falkenstein :
https://fsn1.your-objectstorage.com- Nuremberg :
https://nbg1.your-objectstorage.com- Helsinki :
https://hel1.your-objectstorage.comUtilisez la même région que vos serveurs pour éviter des frais de transfert inter-région.
Tester la connexion depuis le code
# 1. Build l'image Docker backend avec les vars de test
# 2. Ou tester directement avec AWS CLI
# Test avec AWS CLI (profil configuré dans doc 03)
aws s3 ls s3://xpeditis-prod/ \
--profile hetzner \
--endpoint-url https://fsn1.your-objectstorage.com
# Uploader un fichier test
echo "test" | aws s3 cp - s3://xpeditis-prod/test/health.txt \
--profile hetzner \
--endpoint-url https://fsn1.your-objectstorage.com
# Générer une URL signée (1h)
aws s3 presign s3://xpeditis-prod/test/health.txt \
--profile hetzner \
--endpoint-url https://fsn1.your-objectstorage.com \
--expires-in 3600
# Nettoyage
aws s3 rm s3://xpeditis-prod/test/health.txt \
--profile hetzner \
--endpoint-url https://fsn1.your-objectstorage.com
Structure du bucket
Créez les "dossiers" initiaux (S3 utilise des préfixes, pas de vrais dossiers) :
#!/bin/bash
PROFILE="hetzner"
ENDPOINT="https://fsn1.your-objectstorage.com"
BUCKET="xpeditis-prod"
for PREFIX in documents pdfs exports logos backups/postgres; do
aws s3api put-object \
--bucket "$BUCKET" \
--key "$PREFIX/" \
--profile "$PROFILE" \
--endpoint-url "$ENDPOINT" \
--content-length 0
echo "✅ Créé: $PREFIX/"
done
Lifecycle policies (économies de stockage)
Hetzner Object Storage supporte les lifecycle rules S3 pour archiver automatiquement les anciens fichiers.
# Créer le fichier de lifecycle
cat > /tmp/lifecycle.json << 'EOF'
{
"Rules": [
{
"ID": "archive-old-pdfs",
"Status": "Enabled",
"Filter": {
"Prefix": "pdfs/"
},
"Transitions": [
{
"Days": 90,
"StorageClass": "GLACIER"
}
]
},
{
"ID": "archive-old-exports",
"Status": "Enabled",
"Filter": {
"Prefix": "exports/"
},
"Expiration": {
"Days": 365
}
},
{
"ID": "cleanup-old-backups",
"Status": "Enabled",
"Filter": {
"Prefix": "backups/"
},
"Expiration": {
"Days": 30
}
}
]
}
EOF
# Appliquer le lifecycle
aws s3api put-bucket-lifecycle-configuration \
--bucket xpeditis-prod \
--lifecycle-configuration file:///tmp/lifecycle.json \
--profile hetzner \
--endpoint-url https://fsn1.your-objectstorage.com
# Vérifier
aws s3api get-bucket-lifecycle-configuration \
--bucket xpeditis-prod \
--profile hetzner \
--endpoint-url https://fsn1.your-objectstorage.com
CORS (pour upload direct depuis le navigateur)
Si vous implémentez des uploads directs depuis le browser (carrier portal) :
cat > /tmp/cors.json << 'EOF'
{
"CORSRules": [
{
"AllowedHeaders": ["*"],
"AllowedMethods": ["GET", "PUT", "POST", "DELETE", "HEAD"],
"AllowedOrigins": [
"https://app.xpeditis.com",
"https://xpeditis.com"
],
"ExposeHeaders": ["ETag"],
"MaxAgeSeconds": 3000
}
]
}
EOF
aws s3api put-bucket-cors \
--bucket xpeditis-prod \
--cors-configuration file:///tmp/cors.json \
--profile hetzner \
--endpoint-url https://fsn1.your-objectstorage.com
Intégration dans le Secret Kubernetes
Le Secret Kubernetes backend-secrets contiendra les credentials S3. Voir le doc 09 pour les manifests complets, mais voici la section S3 :
apiVersion: v1
kind: Secret
metadata:
name: backend-secrets
namespace: xpeditis-prod
type: Opaque
stringData:
AWS_S3_ENDPOINT: "https://fsn1.your-objectstorage.com"
AWS_ACCESS_KEY_ID: "<hetzner_access_key>"
AWS_SECRET_ACCESS_KEY: "<hetzner_secret_key>"
AWS_REGION: "eu-central-1"
AWS_S3_BUCKET: "xpeditis-prod"
Monitoring du stockage
# Voir la taille totale du bucket
aws s3 ls s3://xpeditis-prod/ \
--recursive \
--human-readable \
--summarize \
--profile hetzner \
--endpoint-url https://fsn1.your-objectstorage.com \
| tail -3
# Output :
# Total Objects: 1234
# Total Size: 4.5 GiB
Tarifs Hetzner Object Storage
| Ressource | Prix |
|---|---|
| Stockage | Inclus dans le pack €4.99/mois (1 TB) |
| Trafic sortant (internet) | Inclus dans le pack (1 TB) |
| Requêtes | Incluses |
| Stockage > 1 TB | €0.0067/TB/heure (~€4.90/TB/mois) |
| Trafic > 1 TB | ~€1/TB |
Pour Xpeditis à 1 000 users (~200 GB de fichiers), le coût est de €4.99/mois fixe.