La build senza database
Quando ho spostato questo sito su Astro,
il cambiamento che mi ha dato più soddisfazione non riguardava i contenuti o il
design. Era in .upsun/config.yaml — il file che dice alla piattaforma cosa
è questo sito. È passato dal descrivere un piccolo sistema distribuito al
descrivere una cartella di file. Questo post è quel file, annotato.
Cosa diceva prima
La versione WordPress dichiarava tre servizi di supporto e un’applicazione che aveva bisogno di tutti e tre:
services:
appdb: { type: mariadb:11.4 }
redis: { type: redis:7.2 }
es: { type: elasticsearch:7.10 }
Un database per i contenuti. Redis per la cache degli oggetti, perché il database da solo non ce la faceva. Elasticsearch perché la ricerca su quei contenuti aveva bisogno di un vero indice. E un deploy hook che svuotava Redis ed eseguiva a ogni deploy una sequenza di formule magiche WP-CLI per mantenere coerente il tutto. Niente di sbagliato — è davvero ciò di cui ha bisogno un WordPress in versione enterprise. Era solo una quantità enorme di macchinari per un sito personale i cui contenuti sono qualche decina di file Markdown.
Cosa dice adesso
Ecco l’intera configurazione dell’applicazione, senza tagliare nulla:
applications:
app:
source: { root: "app" }
type: "nodejs:22"
build:
flavor: none
hooks:
build: |
set -e
npm ci
npm run build
web:
upstream:
socket_family: tcp
commands:
start: "node server.mjs"
locations:
"/":
root: "dist"
index: ["index.html"]
passthru: true
expires: 10m
scripts: false
"/_astro":
root: "dist/_astro"
passthru: true
expires: 1y
Non c’è nessun blocco services:. È tutta qui la storia.
Alcune righe si guadagnano il loro posto:
type: nodejs:22è per lo più un runtime di build. Node esegueastro buildquando faccio push; in fase di serving esegue una sola piccolissima cosa (più sotto), mentre ogni pagina vera è un file statico che il router restituisce direttamente.build.flavor: nonedice alla piattaforma di non tentare di indovinare una build; se ne occupa ilbuildhook.npm ciper un’installazione riproducibile a partire dal lockfile, poinpm run build, che èastro check && astro build— il type-check gira prima della build, così un tipo rotto fa fallire il deploy invece di andare in produzione.commands.start+passthru: truepuntano a uno script Node di circa 15 righe,server.mjs, con esattamente un compito: restituire la pagina 404 personalizzata con un vero status 404. nginx serve ogni file che esiste; solo i mancati riscontri autentici arrivano allo script. È l’unico processo di cui questo sito, per il resto statico, non riesce a liberarsi — perché l’hosting statico non può impostare uno status code su un file che restituisce, quindi un ingenuopassthru: /404.htmlservirebbe la pagina di not-found con un 200.- I due
expiressono l’intera strategia di caching. L’HTML riceve dieci minuti scarsi, perché può cambiare quando ripubblico. Tutto ciò che sta sotto/_astro— CSS, JS e immagini con hash — riceve un anno, perché quei nomi di file hanno un hash del contenuto: se il contenuto cambia, cambia il nome del file, così quello vecchio può restare in cache per sempre senza mai diventare stantìo.
Perché il “niente database” è la funzionalità
Togli di mezzo i servizi e succede qualcosa di chiarificatore alla parte operativa. Non c’è nessun database da fare backup, migrare o farsi violare. Nessuna cache da svuotare, perché l’artefatto è già l’HTML finale. Nessun cluster di ricerca da tenere sincronizzato, perché la ricerca su qualche decina di file non ne ha bisogno. La superficie di attacco è un file server statico.
Il workflow si riduce a primitive di cui mi fido già:
- Il deploy è
git push. Gira il build hook e, se è verde, i nuovi file vanno in produzione. - Il rollback è
git revert. Non c’è nessuno schema da migrare all’indietro, nessun dato da riconciliare — il commit precedente ha costruito un insieme precedente di file, e non fai altro che ricostruirli. - Lo staging è qualsiasi branch. La piattaforma ne costruisce ognuno in un ambiente completo con il proprio URL — nessun database da clonare, quindi un ambiente di preview è semplicemente… la stessa build, puntata su un branch.
La build in sé impiega circa mezzo secondo a produrre ogni pagina del sito. Ogni pagina è pre-costruita, quindi non c’è nulla da renderizzare al volo — l’unica cosa che “gira” mai è il piccolo fallback 404, e solo quando qualcuno richiede una pagina che non esiste.
L’onesto distinguo
“Quasi nessun server” non vuol dire nessun vincolo — vuol dire che i vincoli si spostano. Il gestore del 404 è l’asterisco onesto: questa piattaforma non può impostare uno status 404 su un file puramente statico, quindi una risposta di not-found autentica costa un piccolissimo processo sempre attivo. E un form di contatto ora ha bisogno di un endpoint esterno, perché non c’è più il PHP a ricevere una POST. Qualsiasi cosa davvero dinamica — contenuti per singolo utente, un sistema di commenti — richiederebbe un servizio da riaggiungere deliberatamente, oppure una terza parte. Per un sito professionale di un solo autore, quello scambio è un affare: ho rinunciato a capacità che non stavo usando per eliminare un’intera categoria di cose che possono rompersi alle 3 del mattino.
Il miglior diff infrastrutturale è quello che rimuove infrastruttura. La configurazione di questo sito ora entra in una schermata, non descrive nessuna parte in movimento e non ha richiesto un solo pensiero fuori orario da quando è andata in produzione. È tutto qui il senso della proposta.