Créer un pipeline d'envoi et d'optimisation d'images
Envoyez des originaux privés vers DigitalOcean Spaces, générez des variantes WebP réactives avec Functions et déployez le flux sécurisé sur App Platform.
Ce que vous allez créer
Ce guide crée un pipeline volontairement simple mais proche de la production. 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.
Utilisez-le pour des avatars, annonces, portfolios ou autres charges synchrones modestes. Ce n’est pas un système de comptes, d’antivirus ou de file durable. Le projet de départ accepte JPEG, PNG et WebP jusqu’à 10 Mo ; ajoutez authentification et quotas avant d’accepter du trafic non fiable.
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.
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 SpacesCré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.
- Original privé : uploads/<uuid>/original
- Variante publique : images/<uuid>/<largeur>.webp
- Base CDN : https://YOUR_BUCKET.YOUR_REGION.cdn.digitaloceanspaces.com
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.
git clone https://github.com/TylorMayfield/digitalocean-image-upload-pipeline.gitSet-Location digitalocean-image-upload-pipelinegit checkout 5b8eafad6e2ec0f0ea554af270e349717d1b182fnpm ciCopy-Item .env.example .env.localnpm run devLe serveur local démarre après la configuration de .env.local.
Déployer les deux composants dans une application App Platform
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.
- 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.
- 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.
- 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.
- 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.
- 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.
Vérifier le résultat
- Résultat attendu
- L’original existe uniquement sous le préfixe privé uploads/ et la réponse liste des URL WebP 400, 800 et 1600 pixels sous images/.
- Arrêter si
- Arrêtez si une URL d’original est publique, si le navigateur voit un secret Spaces ou Function, ou si un envoi non-image/trop volumineux est accepté.
- Étape suivante
- Ouvrez une variante CDN en fenêtre privée, puis ajoutez authentification et quotas avant d’accepter de vrais envois utilisateurs.