Ospitare un sito statico in un container

created: mercoledì, dic 10, 2025

La maggior parte dei siti che ospitiamo è renderizzata staticamente, costruita con strumenti come Hugo, Zola o Jekyll. In generale, tutti questi generatori di siti prendono input semplificati (di solito Markdown) e generano HTML ben definito come output. Questo solleva la domanda: come posso ospitare un sito del genere?

Esistono provider specializzati che offrono hosting per siti statici, ma come sapete abbiamo seguito una strada diversa nella costruzione della nostra infrastruttura centrale. Per la nostra cloud, il denominatore comune per il deployment è un container. E con questo arriva la domanda: come passo da una build di sito statico a un container che ospita il sito web?

Quindi iniziamo da zero generando una pagina molto semplice hello world 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

Poi la nostra personale pagina “Hello World” appare su http://localhost:1313

Fin qui tutto bene. Ora consideriamo di mettere questo in un container.

Due approcci alla containerizzazione

Tecnicamente, possiamo seguire due strade diverse qui:

  1. Mettere tutto il sorgente in un container ed eseguire il processo di build al suo interno
  2. Compilare in locale e copiare solo gli output nel container

Per rendere il nostro esempio più riproducibile e meno dipendente dagli ambienti locali, scegliamo l’opzione 1 per questo scenario. Questo rende anche le pipeline CI/CD più pulite poiché l’ambiente di build è completamente definito nel Dockerfile.

Costruire l’immagine del container

Un’immagine container inizia sempre con un’immagine base. Per questo, usiamo Alpine Linux poiché è piccola (circa 5MB) e fornisce gli strumenti necessari per il nostro progetto.

Useremo una multi-stage build, una tecnica supportata dagli strumenti moderni di build dei container che ci permette di usare un’immagine per la compilazione e un’altra per l’esecuzione. Questo mantiene la nostra immagine finale piccola escludendo gli strumenti di build che non ci servono a runtime.

Stage 1: Build

FROM alpine AS build
RUN apk add --no-cache hugo
WORKDIR /src
ADD . .
RUN hugo --minify

In questo stage, noi:

Il sito costruito finisce 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 questo stage, noi:

Il Dockerfile completo

Ecco il Dockerfile completo che combina 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"]

Salva questo come Dockerfile nella root del tuo progetto Hugo.

Costruire ed eseguire in locale

Puoi usare qualsiasi strumento container compatibile con OCI come Podman o Docker. Gli esempi sotto usano docker, ma podman funziona come sostituto drop-in.

Per costruire l’immagine:

docker build -t my-website .

Per eseguirla in locale:

docker run -p 8080:80 --rm my-website

Il tuo sito è ora disponibile su http://localhost:8080

Il flag -p 8080:80 mappa la porta 8080 della tua macchina alla porta 80 dentro al container. Il flag --rm rimuove automaticamente il container quando si arresta.

Perché questo approccio funziona bene

Questa configurazione ha diversi vantaggi:

  1. Dimensione ridotta dell’immagine: L’immagine finale contiene solo Alpine (~5MB) + lighttpd (~1MB) + i tuoi file HTML. Niente Node.js, niente Ruby, nessun tool di build che gonfia l’immagine di produzione.

  2. Build riproducibili: La stessa versione esatta di Hugo viene eseguita in CI come in locale, eliminando i problemi del tipo “funziona sulla mia macchina”.

  3. Avvio rapido: lighttpd si avvia in millisecondi, rendendo questo perfetto per i deployment scale-to-zero su DTZ.

  4. Sicurezza: Il container di produzione ha una superficie di attacco minima - solo un server di file statici senza runtime dinamico.

Considerazioni sull’architettura

Se stai costruendo 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 tuo container funzioni correttamente su DTZ (e sulla maggior parte degli altri provider cloud), dovresti specificare esplicitamente la piattaforma target durante la build.

Cambia il tuo 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, assicurando la compatibilità indipendentemente dalla macchina su cui costruisci.

Distribuire su DownToZero

Una volta che la tua immagine è costruita, puoi inviarla a un registro container e distribuirla su DTZ. Se stai usando il nostro registry container:

# Tag for DTZ registry
docker tag my-website YOUR_CONTEXT_ID.cr.dtz.dev/my-website:latest

# Login and 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 di DTZ puntando alla tua immagine. Il servizio gestirà automaticamente i certificati TLS, lo scaling e il routing.

Per distribuzioni automatizzate ad ogni commit, dai un’occhiata alla nostra GitHub Action per distribuzioni automatiche.

Adattamento per altri generatori di siti statici

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 rimane lo stesso - basta copiare da /src/public (Zola) o /src/_site (Jekyll) nella document root di lighttpd.

Conclusione

Containerizzare siti statici è semplice una volta che capisci il modello: costruire in uno stage, servire da un altro. Il risultato è un container piccolo, veloce e sicuro, perfetto per i deployment cloud moderni.

Questo approccio si allinea bene con la nostra filosofia a DTZ - uso minimo delle risorse, cold start rapidi e infrastruttura che scala a zero quando non è in uso. Un sito statico in un container di ~10MB che si avvia istantaneamente è il più efficiente possibile per l’hosting web.