La maggior parte dei nostri siti ospitati è renderizzata staticamente, costruita con strumenti come Hugo, Zola o Jekyll. In generale, tutti questi generatori di siti prendono in input una forma semplificata (di solito Markdown) e producono come output HTML ben definito. Ci si pone quindi la domanda: come posso ospitare un sito del genere?
Esistono fornitori specializzati che offrono hosting per siti statici, ma come sapete, abbiamo scelto una strada diversa per costruire la nostra infrastruttura core. Per il nostro cloud, il denominatore comune per il deployment è un container. E con questo sorge la domanda: come passo da un build di un sito statico a un container che ospita il sito web?
Iniziamo quindi da zero generando una pagina “hello world” molto semplice con Hugo.
hugo new site hello
cd hello
git init
git submodule add https://github.com/theNewDynamic/gohugo-theme-ananke.git themes/ananke
echo "theme = 'ananke'" >> hugo.toml
Per verificare la build, possiamo avviare il server in locale:
hugo serve
Apparirà quindi la nostra pagina “Hello World” all’indirizzo http://localhost:1313
Fin qui tutto bene. Ora consideriamo di mettere tutto questo in un container.
Tecnicamente, possiamo seguire due strade diverse:
Per rendere il nostro esempio più riproducibile e meno dipendente dall’ambiente locale, scegliamo l’opzione 1 in questo scenario. Questo rende anche le pipeline CI/CD più pulite, poiché l’ambiente di build è completamente definito nel Dockerfile.
Un’immagine container inizia sempre da un’immagine base. Per questo usiamo Alpine Linux poiché è piccola (circa 5MB) e fornisce gli strumenti necessari per il nostro progetto.
Utilizzeremo un build multi-stage, una tecnica supportata dagli strumenti moderni di costruzione container che permette di usare un’immagine per la build e un’altra per l’esecuzione. In questo modo manteniamo l’immagine finale piccola escludendo gli strumenti di build non necessari in fase di esecuzione.
Stage 1: Build
FROM alpine AS build
RUN apk add --no-cache hugo
WORKDIR /src
ADD . .
RUN hugo --minify
In questa fase:
--minify per produrre un output ottimizzatoIl sito costruito si troverà in /src/public/.
Stage 2: Runtime
FROM alpine AS runner
RUN apk add --no-cache lighttpd
COPY --from=build /src/public /var/www/localhost/htdocs
EXPOSE 80
CMD ["lighttpd", "-D", "-f", "/etc/lighttpd/lighttpd.conf"]
In questa fase:
-D)Ecco il Dockerfile completo che unisce entrambi gli stage:
# Stage 1: Build the static site
FROM alpine AS build
RUN apk add --no-cache hugo
WORKDIR /src
ADD . .
RUN hugo --minify
# Stage 2: Serve with lighttpd
FROM alpine AS runner
RUN apk add --no-cache lighttpd
COPY --from=build /src/public /var/www/localhost/htdocs
EXPOSE 80
CMD ["lighttpd", "-D", "-f", "/etc/lighttpd/lighttpd.conf"]
Salvalo come Dockerfile nella radice del progetto Hugo.
Puoi usare qualsiasi tool container compatibile OCI come Podman o Docker. Gli esempi qui sotto usano docker, ma podman funziona come sostituto diretto.
Per costruire l’immagine:
docker build -t my-website .
Per eseguirla in locale:
docker run -p 8080:80 --rm my-website
Il tuo sito sarà ora disponibile su http://localhost:8080
Il flag -p 8080:80 mappa la porta 8080 della tua macchina sulla porta 80 del container. Il flag --rm rimuove automaticamente il container quando si ferma.
Questa configurazione ha diversi vantaggi:
Dimensione ridotta dell’immagine: l’immagine finale contiene solo Alpine (~5MB) + lighttpd (~1MB) + i file HTML. Niente Node.js, niente Ruby, nessun tool di build che appesantisce l’immagine di produzione.
Build riproducibili: esegue esattamente la stessa versione di Hugo in CI e in locale, eliminando problemi del tipo “funziona sulla mia macchina”.
Avvio rapido: lighttpd parte in millisecondi, rendendo questo setup perfetto per deployment scale-to-zero su DTZ.
Sicurezza: il container di produzione ha una superficie di attacco minima - solo un server di file statici senza runtime dinamico.
Se costruisci la tua immagine su un Mac Apple Silicon (ARM64) o un’altra architettura non standard, ricorda che i server tipicamente girano su AMD64 (x86_64). Per assicurarti che il container funzioni correttamente su DTZ (e la maggior parte degli altri provider cloud), specifica esplicitamente la piattaforma target durante la build.
Modifica il comando di build in:
docker build --platform linux/amd64 -t my-website .
Questo dice a Docker di cross-compilare l’immagine per i server Linux standard, garantendo compatibilità indipendentemente dalla macchina usata per costruire.
Una volta costruita l’immagine, puoi caricarla su un registry container e deployarla su DTZ. Se usi il nostro container registry:
# Tag per il registry DTZ
docker tag my-website YOUR_CONTEXT_ID.cr.dtz.dev/my-website:latest
# Login e push
docker login YOUR_CONTEXT_ID.cr.dtz.dev -u apikey
docker push YOUR_CONTEXT_ID.cr.dtz.dev/my-website:latest
Poi crea un servizio container nella dashboard DTZ puntando alla tua immagine. Il servizio gestirà automaticamente certificati TLS, scalabilità e routing.
Per deployment automatici ad ogni commit, dai un’occhiata alla nostra GitHub Action per deployment senza interruzioni.
Lo stesso schema funziona per altri generatori. Ecco le modifiche chiave:
Per Zola:
FROM alpine AS build
RUN apk add --no-cache zola
WORKDIR /src
ADD . .
RUN zola build
Per Jekyll:
FROM ruby:alpine AS build
RUN apk add --no-cache build-base
RUN gem install bundler jekyll
WORKDIR /src
ADD . .
RUN bundle install
RUN bundle exec jekyll build
Lo stage runtime resta lo stesso - basta copiare da /src/public (Zola) o /src/_site (Jekyll) nella root documenti di lighttpd.
Containerizzare siti statici è semplice una volta compreso il pattern: costruisci in uno stage, servi da un altro. Il risultato è un container piccolo, veloce e sicuro, perfetto per i deployment cloud moderni.
Questo approccio si allinea bene con la filosofia DTZ - risorse minime, cold start rapidi e infrastruttura che scala fino a zero quando non usata. Un sito statico in un container di ~10MB che parte istantaneamente è probabilmente il modo più efficiente di fare hosting web.