Stavo per fare overengineering sul mio blog. Di nuovo.

Ghost su Docker come headless CMS, Astro come frontend, webhook per il rebuild, reverse proxy con OpenResty. Un’architettura da startup SaaS per un blog personale che, siamo onesti, pubblicherà due post al mese.

Poi mi sono fermato, ho guardato il docker-compose e mi sono chiesto: ma perché mantenere due container per qualcosa che posso fare con dei file .md in un repo Git?

Ecco il percorso completo, con tutti i ripensamenti.

Il contesto

Gestisco infrastrutture server per lavoro. So cosa significa mantenere WordPress con 18.000 articoli, WPML che esplode, database da 2.5GB, Redis, CrowdSec, backup. So anche cosa significa quando un container si rompe alle 3 di notte.

Per il mio blog personale volevo esattamente l’opposto: zero manutenzione, zero container, zero database. Performance perfette di default.

Fase 1. Quanto sono veloci i framework moderni?

Prima di scegliere, ho guardato i numeri. I dati HTTP Archive e Chrome UX Report del 2025 parlano chiaro:

Il 60% dei siti costruiti con Astro raggiunge una valutazione “Good” sui Core Web Vitals. WordPress è al 38%, Gatsby al 45%. Astro risulta circa il 40% più veloce dei framework basati su React, con il 90% in meno di JavaScript inviato al browser grazie alla Islands Architecture.

Tradotto: un sito Astro statico fa Lighthouse 100/100 senza nessuno sforzo. Non devi ottimizzare niente, è veloce by default.

La scelta del framework era fatta. Restava il CMS.

Confronto CMS: Ghost vs WordPress vs Storyblok vs Markdown

Ho valutato quattro opzioni serie. Le riassumo per chi sta facendo la stessa scelta.

Ghost è il più elegante. Editor bellissimo, newsletter nativa, Content API pulita, self-hosted gratuito. Il problema? Zero campi custom, zero contenuti strutturati. Funziona perfettamente come blog/newsletter, meno bene per tutto il resto.

WordPress lo conosco fin troppo bene. Ecosistema plugin infinito, familiare. Ma pesante, superficie d’attacco enorme, manutenzione costante. Per un blog personale nel 2026 è overkill.

Storyblok è interessante se hai bisogno di un editor visual e campi custom. Ma è SaaS, il costo scala con l’uso, e per un blog personale è troppo.

Markdown in Astro, zero infrastruttura, zero manutenzione, zero costo. Editing in un text editor. Per un blog tecnico dove scrivi code block ogni tre paragrafi, è il formato naturale.

Fase 3. La prima decisione (sbagliata)

La prima architettura era questa:

  • Ghost su Docker (sulla VPS che uso per altri progetti) come CMS headless
  • Astro come frontend su Cloudflare Pages
  • OpenResty come reverse proxy per l’admin Ghost
  • Webhook Ghost → Cloudflare per rebuild automatico a ogni pubblicazione

L’idea era buona: scrivo su Ghost con il suo editor fantastico, il frontend statico viene ricostruito automaticamente. Il meglio dei due mondi.

Ho persino iniziato a configurare il docker-compose.yml:

# docker-compose.yml - Ghost headless (poi scartato)
services:
  ghost:
    image: ghost:5-alpine
    restart: always
    ports:
      - "127.0.0.1:2368:2368"
    environment:
      url: https://alessandrodecenzo.it
      database__client: mysql
      database__connection__host: ghost-db
      # ...
    volumes:
      - ghost-content:/var/lib/ghost/content

  ghost-db:
    image: mysql:8.0
    restart: always
    volumes:
      - ghost-db:/var/lib/mysql

Due container, un database MySQL, volumi persistenti, backup da gestire. Per un blog.

Fase 4. Il ripensamento

Il trigger è stato semplice: ho deciso che non mi serviva una newsletter. Almeno non subito.

Senza newsletter, Ghost perde il suo vantaggio killer. Diventa un editor Markdown con API, e io sto mantenendo due container Docker per qualcosa che posso fare con dei file .md in una cartella.

Il calcolo era ovvio:

Ghost headless significava 2 container sulla VPS, backup database, aggiornamenti Ghost, reverse proxy da configurare, webhook da mantenere, e circa 20€/mese di risorse server.

Markdown nel repo significava zero container, zero backup (Git è già il backup), zero manutenzione, zero costo infrastruttura. Solo 13€/anno per il dominio.

Fase 5. La decisione finale

Lo stack definitivo:

  • Content: file .md nel repo GitHub, con Astro 5 Content Collections
  • Build: Astro 5 (static site generation)
  • Deploy: Cloudflare Pages, free tier, edge globale, ~50ms TTFB
  • Dominio: alessandrodecenzo.it su Keliweb

Il workflow è: scrivo un .md, pusho su main, Cloudflare Pages fa il build e deploy automatico. Fine.

---
title: "Il mio primo post"
description: "Descrizione per SEO e social"
date: 2026-02-25
tags: ["astro", "blog"]
---

Contenuto in Markdown. Code block, immagini, tutto supportato.

Costo totale: ~13€/anno. Solo il dominio.

Fase 6. Il design

Volevo un sito che comunicasse “developer, terminale, tecnico” a colpo d’occhio. Il contrario del sito dell’agenzia (che è light-first, minimal corporate).

Palette dark-first. Sfondo #141414, card #1c1c1c, code blocks #0e0e0e. Rosso accent in due varianti: #F05050 per il testo su sfondi scuri (link, tag, evidenziazioni) e #D4162C per gli sfondi dei bottoni con testo bianco sopra.

Il problema dell’accessibilità. Ho scritto uno script Python per verificare il contrasto WCAG AA su tutte le combinazioni colore della palette:

# check_contrast.py - Verifica WCAG AA su tutte le combinazioni
import itertools

def relative_luminance(hex_color):
    """Calcola la luminanza relativa di un colore HEX."""
    r, g, b = int(hex_color[1:3], 16), int(hex_color[3:5], 16), int(hex_color[5:7], 16)
    rgb = []
    for c in [r, g, b]:
        c = c / 255.0
        c = c / 12.92 if c <= 0.03928 else ((c + 0.055) / 1.055) ** 2.4
        rgb.append(c)
    return 0.2126 * rgb[0] + 0.7152 * rgb[1] + 0.0722 * rgb[2]

def contrast_ratio(hex1, hex2):
    """Calcola il rapporto di contrasto tra due colori."""
    l1 = relative_luminance(hex1)
    l2 = relative_luminance(hex2)
    lighter = max(l1, l2)
    darker = min(l1, l2)
    return (lighter + 0.05) / (darker + 0.05)

# Palette completa
backgrounds = {
    "bg-code": "#0e0e0e",
    "bg-primary": "#141414",
    "bg-surface": "#1c1c1c",
    "bg-inline": "#252525",
}
texts = {
    "text-bright": "#eeeeee",
    "text-primary": "#d4d4d4",
    "text-secondary": "#909090",
    "text-muted": "#848484",
    "accent": "#F05050",
    "green": "#5ec269",
}

# Verifica tutte le combinazioni
for (bg_name, bg_hex), (txt_name, txt_hex) in itertools.product(
    backgrounds.items(), texts.items()
):
    ratio = contrast_ratio(bg_hex, txt_hex)
    status = "PASS" if ratio >= 4.5 else "FAIL (AA)"
    # AA per testo grande: >= 3.0, per testo normale: >= 4.5
    print(f"{txt_name} on {bg_name}: {ratio:.2f}:1 [{status}]")

Risultato: 9 combinazioni su circa 24 non passavano WCAG AA nella prima iterazione. Il fix principale è stato introdurre il doppio livello di rosso: #F05050 (più chiaro, 4.84:1 minimo sui fondi scuri) per il testo e #D4162C (l’originale _blank) solo per i bottoni dove il testo sopra è bianco.

Typography. JetBrains Mono per headings, code e navigazione, dà il feel “terminale” a tutta l’interfaccia. Inter per il body text, perché la leggibilità su long-form è eccellente.

Code blocks. Niente pallini rosso/giallo/verde stile macOS (è un cliché). Header stile terminale con path del file e tag del linguaggio.

Performance Astro: i numeri reali (Lighthouse, Core Web Vitals)

Performance del sito con questo stack, misurate con Lighthouse su connessione simulata 4G:

MetricaAstro + MarkdownWordPress ottimizzatoGhost
Performance100/10085-9590-98
Accessibility100/10085-9590-95
TTFB~50ms200-500ms100-200ms
First Contentful Paint<0.5s1-2s0.5-1s
Total Blocking Time0ms50-200ms10-50ms
JS shipped~0 KB200-500 KB50-100 KB
Costo hosting€13/anno€5-20/mese€5-15/mese

Limiti di Markdown puro e quando scegliere Ghost o WordPress

Markdown puro non è per tutti. Scrivi in un text editor, non hai preview live nel browser (a meno di tenere npm run dev attivo), non hai un pannello con bottoni per grassetto e corsivo. Se non ti è naturale scrivere in Markdown, è una scelta sbagliata. Alternative come TinaCMS o Decap CMS possono aggiungere un editor visual sopra i file Markdown nel repo, il meglio dei due mondi, se ne hai bisogno.

Se servisse la newsletter, dovrei aggiungere un servizio esterno (Buttondown, ConvertKit, o Mailchimp). È il tradeoff di aver tolto Ghost: ho semplificato l’infrastruttura ma ho perso la newsletter integrata.

Il “deployment via Git push” è fantastico se sei un developer. Se dovessi far scrivere un non-tecnico su questo blog, Ghost o WordPress sarebbero scelte migliori.

Ma per un blog tecnico personale dove il 50% del contenuto è codice? File Markdown nel repo è la risposta più onesta.