xpeditis2.0/docs/deployment/hetzner/06-storage-s3.md
2026-03-26 18:08:28 +01:00

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.com

Utilisez 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.