← Retour à tous les articles

Créer un pipeline d'envoi et d'optimisation d'images

Explorez un prototype jetable avec originaux privés dans Spaces et variantes WebP ; ajoutez authentification, autorisation et quotas avant tout accès public.

Dépôt GitHub ↗

Le navigateur envoie un original directement dans un stockage privé. Une petite API démarre une Function sécurisée, puis seules les variantes optimisées arrivent au CDN.

Ce que vous allez créer

Ce guide présente un prototype local ou jetable à utiliser avec des images de démonstration non sensibles. Le navigateur ne reçoit jamais un secret Spaces. Il demande une URL d’envoi temporaire à votre application App Platform, envoie l’original vers une clé privée, puis demande à l’application de démarrer une Function sécurisée. La Function crée des variantes WebP de 400, 800 et 1600 pixels et renvoie leurs URL CDN.

Le prototype ne fournit ni authentification des utilisateurs ni quotas individuels. Toute personne pouvant joindre ses API peut créer du stockage et du traitement facturés. Placez un contrôle d’accès avant tout déploiement accessible publiquement, même pour un petit essai. La limite JPEG, PNG et WebP de 10 Mo ne constitue pas une protection contre les abus.

Pour le premier test, utilisez une image d’au moins 1600 pixels de large après orientation, sous 10 Mo. Le traitement n’agrandit pas les images : un original plus petit peut donner des fichiers de noms différents mais de même largeur. Vérifiez les dimensions en plus des trois URL.

  • App Platform héberge l’interface Next.js et garde les secrets côté serveur.
  • Spaces stocke les originaux privés sous uploads/ et les dérivés publics sous images/.
  • Une Function Node.js avec Sharp traite des clés objet, pas des corps de requête image.
  • Le CDN Spaces sert uniquement les variantes finales.

Comprendre l’architecture et la frontière de sécurité

La frontière importante est l’envoi direct vers le stockage. Faire passer une image complète par une route API consomme inutilement la bande passante de l’application. POSTez plutôt les métadonnées à /api/uploads, recevez une URL PUT présignée de cinq minutes, puis envoyez directement vers Spaces. La réponse contient un UUID imprévisible, jamais le nom de fichier original.

Après le PUT, le navigateur POSTe cet UUID vers /api/images/:id/process sur le même domaine. Cette route appelle la Function avec son secret web. Ni l’URL de la Function ni la valeur X-Require-Whisk-Auth ne parviennent au navigateur.

TEXT
Navigateur → POST /api/uploads → API App Platform
Navigateur → PUT vers l’URL présignée → Spaces uploads/<uuid>/original (privé)
Navigateur → POST /api/images/<uuid>/process → API App Platform
API App Platform → Function sécurisée → Spaces images/<uuid>/{400,800,1600}.webp
Navigateur ← manifeste avec les URL publiques du CDN Spaces

Créer un Space pour les originaux et les variantes

Créez un bucket Spaces standard dans la même région que l’application et activez son CDN. Gardez la liste de fichiers désactivée. L’application écrit les originaux avec une ACL privée et écrit les fichiers générés avec une ACL public-read. Vous obtenez ainsi un stockage unique sans exposer accidentellement le chemin d’envoi.

Configurez CORS pour n’autoriser PUT que depuis l’origine App Platform déployée et pour l’en-tête Content-Type. N’utilisez pas une origine générique en production.

Configurer les secrets de l’application et de la Function

Clonez le projet compagnon et examinez .env.example pour préparer les variables chiffrées du déploiement. Le serveur local ci-dessous est facultatif. Pour tester le traitement localement, déployez d'abord la Function à l'étape suivante, puis copiez son URL et les autres valeurs dans .env.local. Les clés existent seulement comme variables chiffrées App Platform et dans la configuration de Functions ; ne leur donnez jamais le préfixe NEXT_PUBLIC_.

Le fichier project.yml rend l’action accessible sur le web tout en exigeant le même secret via X-Require-Whisk-Auth. Utilisez une compilation distante pour Sharp : les dépendances natives doivent être compilées pour le runtime Functions.

Commande shell
git clone https://github.com/TylorMayfield/digitalocean-image-upload-pipeline.git
cd digitalocean-image-upload-pipeline
git checkout 5b8eafad6e2ec0f0ea554af270e349717d1b182f
npm ci
cp .env.example .env.local
npm run dev

Déployer les deux composants dans une application App Platform

Avant les étapes suivantes, ajoutez authentification et autorisation côté serveur devant /api/uploads et /api/images/:id/process, ou protégez tout le service par un accès privé. Définissez des quotas individuels et une limite de dépenses. Le dépôt compagnon ne fournit pas ces contrôles : ne publiez pas le prototype inchangé. CORS et le secret de la Function n’authentifient pas les visiteurs de l’API web.

Contrôle négatif : depuis une session déconnectée, essayez de préparer un envoi puis de traiter une ressource appartenant à un autre utilisateur. Les deux requêtes doivent être rejetées avant toute URL signée ou invocation, sans nouvel objet ni traitement enregistré. Répétez avec un compte valide qui dépasse son quota. Continuez vers le déploiement payant seulement après réussite de ces contrôles.

Utilisez App Platform pour déployer les deux composants depuis GitHub. Vous pouvez effectuer ce déploiement dans le navigateur, sans installer doctl. Le composant Functions est construit à distance par App Platform, y compris les dépendances natives de Sharp.

Le service web et la Function utilisent les variables SPACES_BUCKET, SPACES_REGION, SPACES_KEY, SPACES_SECRET et SPACES_CDN_BASE_URL. Configurez aussi FUNCTION_AUTH_TOKEN avec le même secret long dans les deux composants. Seul le service web reçoit FUNCTION_URL. Gardez toutes ces valeurs dans les variables chiffrées du tableau de bord.

La révision 5b8eafa du dépôt contient le service web et l’action Functions avec index.js et ses dépendances dans functions/packages/image/process/. Vérifiez cette structure dans votre copie avant le déploiement. Les tests de compilation et de traitement d’image passent ; le déploiement avec vos identifiants reste à vérifier.

  1. Créez votre copie du dépôt compagnon sur GitHub. Dans .do/app.yaml, remplacez les deux valeurs REPLACE_WITH_YOUR_GITHUB_REPOSITORY par votre propriétaire/nom-du-dépôt, puis enregistrez le fichier dans Git.
  2. Dans App Platform, créez une application depuis ce dépôt et vérifiez sa spécification. Elle doit contenir le service web, source /, et image-functions, source /functions. Si Functions manque, ajoutez ce composant depuis le même dépôt avant de continuer.
  3. Dans les réglages des composants, ajoutez les variables chiffrées indiquées ci-dessus. Laissez FUNCTION_URL absent du service web pour ce premier déploiement. La page peut démarrer, mais le traitement d'image restera indisponible jusqu'à la prochaine étape.
  4. Déployez et vérifiez les journaux de compilation de image-functions. Lorsque image/process est disponible, copiez son URL web depuis App Platform. Ajoutez cette URL comme FUNCTION_URL au service web, puis redéployez ce service.
  5. Ajoutez l'origine HTTPS du service web à la règle CORS du bucket. Passez ensuite au test d'une image. Aucun déploiement vers un namespace Functions séparé n'est nécessaire.

Envoyer une image et vérifier le résultat

Ouvrez l’application déployée, choisissez un JPEG, PNG ou WebP inférieur à 10 Mo et envoyez-le. La page indique d’abord que l’original est arrivé dans Spaces, puis affiche les URL des variantes. Vérifiez que uploads/<uuid>/original reste privé et que seules les trois clés sous images/<uuid>/ sont publiques.

Ouvrez une URL CDN dans une fenêtre privée. Elle doit fonctionner sans chaîne de requête signée. Si ce n’est pas le cas, vérifiez l’ACL de l’objet, puis l’endpoint CDN. Ne rendez jamais uploads/ public juste pour réussir ce test.

Connaître les limites avant de passer à l’échelle

La Function reçoit un petit objet JSON avec un identifiant, jamais les octets d’image. Le traitement reste toutefois synchrone. DigitalOcean Functions impose des limites de mémoire, délai, taille de fonction et requête/réponse. Ajustez mémoire et délai à vos images testées, puis refusez les entrées hors de cette enveloppe.

Pour les transformations longues, un gros volume, la vidéo ou les reprises, utilisez une file et un worker. Ajoutez vérification du contenu, limitation de débit, autorisation, suppression planifiée et antivirus avant de traiter ce projet comme une plateforme de contenu utilisateur.

Questions fréquentes

Pourquoi ne pas envoyer le fichier à travers une route App Platform ?

Une URL PUT présignée laisse le navigateur envoyer directement vers Spaces ; votre API ne traite que les métadonnées et l’autorisation. Cela évite de déplacer les octets d’image dans une requête API.

Pourquoi les originaux sont-ils privés et les variantes publiques ?

Un original peut contenir des métadonnées ou être plus grand que nécessaire. Le garder privé réduit son exposition, tandis que les variantes WebP générées peuvent être mises en cache par le CDN.

Puis-je utiliser ce projet pour une grande médiathèque ?

Conservez cette frontière de stockage, mais ajoutez comptes, quotas, validation, tâches durables, reprises, nettoyage et observabilité. Une Function synchrone est un bon premier pipeline, pas une plateforme média complète.

Limites DigitalOcean Functions (8 October 2026).