Obelica Docs
Operazioni

Storage Spaces

DigitalOcean Spaces per asset applicativi e migrazione da Vercel Blob

Scopo

DigitalOcean Spaces e' lo storage oggetti standard per gli asset applicativi che devono uscire da Vercel Blob o da provider esterni durante la migrazione verso il VPS.

Questa non e' una dipendenza obbligatoria per ogni app. Va usata solo dove esiste un bisogno reale di storage file in produzione.

Spazi creati

SpaceAmbienteRegioneCDNOrigin endpoint
obelica-assets-prodproduzionefra1abilitatohttps://obelica-assets-prod.fra1.digitaloceanspaces.com
obelica-assets-stagingstagingfra1abilitatohttps://obelica-assets-staging.fra1.digitaloceanspaces.com

Endpoint API S3-compatible:

https://fra1.digitaloceanspaces.com

Scope applicativo

Decisione del 2026-06-22:

AppPolicy storage
sonja-trekkingtarget migrazione storage; usa Vercel Blob per upload/delete immagini tour
tulpa-studiotarget migrazione storage; contiene flussi Vercel Blob e Cloudinary, da razionalizzare con cautela
alessandro-zucchinimigrazione production Cloudinary -> Spaces completata; non era un target Vercel Blob
wash-dog-ferraraesclusa dalla migrazione storage per ora; eventuale codice Blob non va considerato produzione reale
obelicaesclusa dalla migrazione storage per ora

Le altre app non vanno collegate a Spaces finche' non emerge un requisito reale.

Variabili ambiente

Template produzione:

STORAGE_PROVIDER=spaces
SPACES_REGION=fra1
SPACES_BUCKET=obelica-assets-prod
SPACES_ENDPOINT=https://fra1.digitaloceanspaces.com
SPACES_ORIGIN_URL=https://obelica-assets-prod.fra1.digitaloceanspaces.com
SPACES_PUBLIC_BASE_URL=https://obelica-assets-prod.fra1.cdn.digitaloceanspaces.com
SPACES_ACCESS_KEY_ID=<secret>
SPACES_SECRET_ACCESS_KEY=<secret>

Template staging:

STORAGE_PROVIDER=spaces
SPACES_REGION=fra1
SPACES_BUCKET=obelica-assets-staging
SPACES_ENDPOINT=https://fra1.digitaloceanspaces.com
SPACES_ORIGIN_URL=https://obelica-assets-staging.fra1.digitaloceanspaces.com
SPACES_PUBLIC_BASE_URL=https://obelica-assets-staging.fra1.cdn.digitaloceanspaces.com
SPACES_ACCESS_KEY_ID=<secret>
SPACES_SECRET_ACCESS_KEY=<secret>

Non committare mai chiavi Spaces o connection string reali.

Prefissi oggetti

Usare prefissi stabili per evitare collisioni e semplificare audit/migrazioni:

<app>/<feature>/<id>/<filename>
sonja-trekking/tours/<tour-id>/<timestamp>.webp
tulpa-studio/cms/media/<media-id>/<filename>
alessandro-zucchini/media/<media-id>/<filename>

Migrazione da Vercel Blob

Procedura target:

  1. Inventariare codice e database della produzione reale.
  2. Ottenere la sorgente reale Vercel Blob/store o l'elenco URL usato in produzione.
  3. Copiare gli oggetti su Spaces mantenendo una mappa old_url -> new_url.
  4. Aggiornare il database con script idempotente e backup pre-migrazione.
  5. Deployare il provider Spaces nell'app.
  6. Verificare upload, lettura pubblica, delete e rollback.
  7. Tenere Vercel Blob in sola lettura finche' la produzione non serve stabilmente gli URL Spaces.

Non eliminare dati da Vercel Blob finche' non esistono:

  • backup DB post-migrazione;
  • mappa di migrazione conservata;
  • smoke test e test manuale delle pagine principali;
  • rollback documentato.

Audit del 2026-06-22

Produzione Vercel

Audit read-only eseguito con Vercel CLI autenticata, scaricando temporaneamente gli env produzione fuori repo e cancellandoli a fine comando. Nessun segreto e' stato documentato.

Inventario Vercel Blob:

AppBlob token produzioneOggetti BlobDimensione totaleHost Blob
sonja-trekkingpresente57132.31 MBpukpqfy6og9fpdh3.public.blob.vercel-storage.com
tulpa-studiopresente267564.01 MBgs0y25qv70rmbqva.public.blob.vercel-storage.com
alessandro-zucchiniassente00 MBn/a

Riferimenti storage nel database produzione Vercel:

AppBlob refsCloudinary refsSpaces refsColonne principali
sonja-trekking5700public.Tour.imageUrl, public.Tour.additionalImages
tulpa-studio327 refs, 210 URL unici00public.media.url, public.product_models.imageUrl, public.projects.coverImageUrl, public.cms_content.content
alessandro-zucchini0280public.Media.url, public.Lesson.videoUrl, public.Lesson.coverImageUrl, public.Portfolio.videoUrls, public.Course.imageUrl, public.Course.previewVideoUrl, public.LessonAttachment.fileUrl, public.Portfolio.thumbnailUrl

Conclusione operativa:

  • sonja-trekking e' il primo candidato ideale per migrazione Vercel Blob -> Spaces: pochi riferimenti e tutti gli URL DB sono presenti nel listing Blob;
  • tulpa-studio e' il secondo candidato, piu' ampio e con piu' feature media;
  • alessandro-zucchini non era una migrazione Vercel Blob: e' stata completata come migrazione Cloudinary -> Spaces il 2026-06-23.

Pilota Sonja Trekking

Decisione operativa del 2026-06-23:

  • Sonja e' il progetto pilota per il passaggio Vercel Blob -> DigitalOcean Spaces.
  • Il codice deve mantenere vercel come provider default finche' la migrazione non e' pronta.
  • Lo switch produzione finale deve avvenire cambiando solo STORAGE_PROVIDER da vercel a spaces.

Env produzione Vercel gia' preparate:

STORAGE_PROVIDER=spaces
SPACES_REGION=fra1
SPACES_BUCKET=obelica-assets-prod
SPACES_ENDPOINT=https://fra1.digitaloceanspaces.com
SPACES_ORIGIN_URL=https://obelica-assets-prod.fra1.digitaloceanspaces.com
SPACES_PUBLIC_BASE_URL=https://obelica-assets-prod.fra1.cdn.digitaloceanspaces.com

Variabili sensitive impostate in Vercel Production:

SPACES_ACCESS_KEY_ID=<secret>
SPACES_SECRET_ACCESS_KEY=<secret>

Stato del 2026-06-23:

  • credenziali Spaces production reinserite come env sensitive Vercel;
  • test credenziali eseguito con put -> HEAD CDN 200 -> delete su oggetto temporaneo;
  • copia Vercel Blob -> Spaces completata per gli oggetti disponibili;
  • backup DB production immediatamente prima del rewrite: .storage-migration/sonja-production-before-spaces-repair-20260622T231345Z.dump;
  • rewrite DB production completato usando DATABASE_URL_UNPOOLED;
  • STORAGE_PROVIDER=spaces attivo in Vercel Production;
  • deployment production Vercel successivo allo switch: sonja-trekking-nkuadjxgo-bona-pro.vercel.app, stato Ready;
  • smoke pubblico su https://www.sonjatrekking.com/it: HTTP 200.

Verifica DB post-migrazione:

imageBlob=0
imageSpaces=21
additionalBlobUrls=0
additionalSpacesUrls=26
toursWithAdditionalBlob=0
toursWithAdditionalSpaces=9
spacesUrlsChecked=46
spacesUrlsFailed=0

Note sui riferimenti rotti:

  • durante il dry-run corretto sono emersi 4 URL Blob presenti nel DB ma non nel listing Vercel Blob;
  • tutti e 4 rispondevano HTTP 404;
  • 3 immagini aggiuntive 404 del tour San Vigilio, tipico ladino sono state rimosse dall'array additionalImages;
  • l'immagine principale 404 del tour Fanes e il fascino delle sue acque e' stata sostituita con la prima immagine aggiuntiva valida dello stesso tour;
  • nessun URL blob.vercel-storage.com rimane nel DB production.

Il codice Sonja introduce:

  • provider storage vercel/spaces;
  • upload immagini via STORAGE_PROVIDER;
  • delete compatibile con URL Vercel Blob e URL Spaces;
  • prefisso oggetti Spaces sonja-trekking/;
  • supporto Next Image per host digitaloceanspaces.com;
  • script migrazione dry-run/copy/apply-db.

Comandi script:

npm run storage:dry-run
npm run storage:copy
npm run storage:apply-db
npm run storage:apply-db:repair

Dry-run produzione del 2026-06-23:

blobObjects=57
referencedBlobUrls=50
mappedReferencedUrls=46
missingReferencedUrls=4
repairMissingReferences=enabled

Sequenza di migrazione Sonja, completata:

  1. Aggiungere le due env sensitive Spaces in Vercel Production.
  2. Deployare il codice storage nuovo lasciando STORAGE_PROVIDER=vercel.
  3. Eseguire storage:dry-run con env produzione e verificare la sorgente DB corretta.
  4. Eseguire storage:copy per copiare tutti gli oggetti Blob in Spaces e salvare la mappa ignorata da git.
  5. Verificare a campione URL Spaces/CDN.
  6. Fare backup DB produzione.
  7. Eseguire storage:apply-db:repair usando la mappa, per gestire i riferimenti 404 gia' rotti.
  8. Verificare sito e admin con STORAGE_PROVIDER=vercel: le immagini devono gia' puntare a Spaces.
  9. Cambiare STORAGE_PROVIDER=spaces in Vercel Production.
  10. Testare upload nuovo, visualizzazione e delete.

Tulpa Studio

Stato operativo del 2026-06-23: migrazione production da Vercel Blob a DigitalOcean Spaces completata.

Risultato finale:

  • Vercel Production tulpa-studio usa STORAGE_PROVIDER=spaces;
  • bucket target obelica-assets-prod, regione fra1, CDN DigitalOcean abilitato;
  • prefisso oggetti Spaces: tulpa-studio/;
  • CORS bucket configurato per https://tulpastudio.com e https://www.tulpastudio.com;
  • deploy production Vercel completato e aliasato a https://www.tulpastudio.com;
  • DB produzione riscritto da URL Vercel Blob a URL CDN DigitalOcean Spaces;
  • Vercel Blob resta da tenere in sola lettura come rollback storico finche' la produzione non e' stabile.

Audit pre-migrazione produzione:

blobObjects=267
blobBytes=591405916
referencedBlobUrls=210
dbReferences=327
missingReferencedUrls=0
orphanedBlobObjects=57
orphanedBlobBytes=208206825

Copia oggetti:

npm run storage:copy -- --env-file=<env>
copy=done
objectsCopied=267/267
mapPath=.storage-migration/tulpa-vercel-blob-to-spaces-map.json

Verifica copia:

map entries=267
HEAD CDN sample=60/60 OK

Backup DB creato immediatamente prima del rewrite:

.storage-migration/tulpa-production-before-spaces-20260623T003824Z.dump

Rewrite DB produzione:

npm run storage:apply-db -- --env-file=<env>
mode=write-capable
databaseUrlSource=DATABASE_URL_UNPOOLED
referencedBlobUrls=210
dbReferences=327
mappedReferencedUrls=210
missingReferencedUrls=0
updatedRows=254
dbRewrite=done

Audit post-migrazione DB produzione:

uniqueBlobUrls=0
uniqueSpacesUrls=210
spacesHost=obelica-assets-prod.fra1.cdn.digitaloceanspaces.com
HEAD all Spaces URLs=210/210 OK

Distribuzione riferimenti URL dopo rewrite:

CampoRiferimenti URL estratti
media.url134
product_models.imageUrl104
projects.coverImageUrl13
projects.vimeoUrl13
site_settings.value2
cms_content.content94

Smoke pubblico post-migrazione:

URLEsito
https://www.tulpastudio.com/HTTP 200
https://www.tulpastudio.com/portfolioHTTP 200
https://www.tulpastudio.com/rentalHTTP 200

Codice storage attivo:

  • provider storage vercel/spaces, con default conservativo vercel;
  • endpoint POST /api/cms/media/upload-target per presigned upload Spaces;
  • upload admin compatibile con Vercel Blob o Spaces;
  • delete/move compatibili con URL Vercel Blob e URL Spaces;
  • supporto Next Image per digitaloceanspaces.com;
  • script migrazione dry-run/copy/apply-db: scripts/migrate-vercel-blob-to-spaces.ts.

Verifica manuale ancora consigliata:

  1. Login admin Tulpa.
  2. Upload nuovo media in media library.
  3. Inserimento media in pagina/prodotto/progetto.
  4. Visualizzazione pubblica.
  5. Delete media test.

Alessandro Zucchini

Stato operativo del 2026-06-23: migrazione production da Cloudinary a DigitalOcean Spaces completata.

Risultato finale:

  • Vercel Production alessandro-zucchini usa STORAGE_PROVIDER=spaces;
  • bucket target obelica-assets-prod, regione fra1, CDN DigitalOcean abilitato;
  • prefisso oggetti Spaces: alessandro-zucchini/;
  • CORS bucket configurato anche per https://www.alessandrozucchini.com, https://alessandrozucchini.com e varianti storiche alessandro-zucchini.com;
  • DB produzione senza URL Cloudinary residui;
  • 17 URL Spaces unici verificati con HEAD HTTP 200;
  • Cloudinary resta supportato nel codice come provider fallback, ma non e' piu' la sorgente storage della produzione.

Deploy produzione:

deploy preparatorio storage adapter: dpl_3tMVDhfeLXt9XHcS4jAWuwYo6W9B
deploy switch STORAGE_PROVIDER=spaces: dpl_9AtPTtvqWMH4QibSswiWeLcW24fg
alias produzione: https://www.alessandrozucchini.com

Audit pre-migrazione produzione:

dbReferences=28
uniqueCloudinaryUrls=18
validCloudinaryUrls=17
missingCloudinaryUrls=1
copyBytesKnown=95601798

Il riferimento mancante era una cover lezione Cloudinary gia' HTTP 404 in produzione, presente in due lezioni di test. La migrazione l'ha riparato impostando Lesson.coverImageUrl a null per quelle due righe.

Copia oggetti:

npm run storage:copy -- --env-file=<env> --repair-missing
copy=done
objectsCopied=17/17
mapPath=.storage-migration/alessandro-cloudinary-to-spaces-map.json
HEAD CDN all copied URLs=17/17 OK

Backup DB creato immediatamente prima del rewrite:

.storage-migration/alessandro-production-before-spaces-20260623T080219Z.dump

Rewrite DB produzione:

npm run storage:apply-db -- --env-file=<env> --repair-missing
mode=write-capable
databaseUrlSource=DATABASE_URL_UNPOOLED
referencedCloudinaryUrls=18
dbReferences=28
mappedReferencedUrls=17
missingReferencedUrls=1
updatedRows=24
repairedRows=2
dbRewrite=done

Audit post-migrazione DB produzione:

totalUrlOccurrences=26
uniqueCloudinaryUrls=0
uniqueSpacesUrls=17
spacesHost=obelica-assets-prod.fra1.cdn.digitaloceanspaces.com
HEAD all Spaces URLs=17/17 OK

Distribuzione riferimenti URL dopo rewrite:

CampoRiferimenti URL estratti
Media.url16
Lesson.videoUrl4
LessonAttachment.fileUrl1
Course.imageUrl1
Course.previewVideoUrl1
Portfolio.thumbnailUrl1
Portfolio.videoUrls2

Smoke pubblico post-migrazione:

URLEsito
https://www.alessandrozucchini.com/itHTTP 200
https://www.alessandrozucchini.com/it/corsiHTTP 200
https://www.alessandrozucchini.com/it/portfolio/carnevale-di-veneziaHTTP 200

Codice storage attivo:

  • provider storage cloudinary/spaces, con default conservativo cloudinary;
  • endpoint POST /api/admin/storage-upload-target per presigned upload Spaces o signed Cloudinary upload;
  • media library admin compatibile con Cloudinary o Spaces;
  • upload server-side e lesson attachments compatibili con Cloudinary o Spaces;
  • delete compatibile con URL Cloudinary e URL Spaces;
  • supporto Next Image per digitaloceanspaces.com;
  • script migrazione dry-run/copy/apply-db: scripts/migrate-cloudinary-to-spaces.ts.

Verifica manuale ancora consigliata:

  1. Login admin Alessandro.
  2. Upload nuovo media in media library.
  3. Upload/delete lesson attachment di test.
  4. Visualizzazione pubblica del media nuovo.
  5. Delete media test.

Database DigitalOcean attuale

Nel managed database DigitalOcean attualmente collegato alle app non sono stati trovati URL contenenti:

  • blob.vercel-storage.com;
  • res.cloudinary.com o cloudinary;
  • digitaloceanspaces.com.

Risultato per DB controllati:

AppMatch storage nel DB DO attuale
alessandro-zucchini0
sonja-trekking0
tulpa-studio0
wash-dog-ferrara0

Questo non prova che la produzione Vercel non abbia asset. Significa solo che il database DigitalOcean oggi non contiene riferimenti storage noti. Prima di migrare dati reali bisogna identificare la sorgente effettiva usata dalla produzione Vercel.

CORS

Il CORS serve solo per upload diretti dal browser verso Spaces. Upload server-side tramite API applicativa non richiede CORS per la scrittura.

Policy consigliata:

  • abilitare CORS solo sulle app che fanno upload diretto browser;
  • usare origin espliciti, non wildcard, per produzione;
  • includere gli origin staging *.vps.obelica.com solo per test;
  • fare purge CDN dopo modifiche CORS se il comportamento non cambia subito.

Sicurezza chiavi

La prima chiave creata per test non deve diventare la chiave permanente di produzione se e' troppo ampia.

Prima della migrazione produzione:

  • creare chiavi granulari per bucket/ambiente quando possibile;
  • separare chiavi staging e produzione;
  • ruotare qualunque chiave condivisa in chat o screenshot;
  • salvare segreti solo nei secret store/env locali previsti;
  • non documentare mai valori segreti in Fumadocs.

On this page