Ein statisches Site in einem Container hosten

created: Mittwoch, Dez. 10, 2025

Die meisten unserer gehosteten Sites werden statisch gerendert und mit Tools wie Hugo, Zola oder Jekyll gebaut. Im Allgemeinen nehmen all diese Site-Renderer vereinfachte Eingaben (meist Markdown) und erzeugen wohldefiniertes HTML als Ausgabe. Damit stellt sich die Frage: Wie kann ich eine solche Site hosten?

Es gibt spezialisierte Anbieter, die Hosting für statische Sites anbieten, aber wie Sie wissen, sind wir beim Aufbau unserer Kerninfrastruktur einen anderen Weg gegangen. Für unsere Cloud ist das gemeinsame Nenner für Deployments ein Container. Und damit stellt sich die Frage: Wie komme ich von einem statischen Site-Build zu einem Container, der die Website hostet?

Fangen wir also von vorn an und erzeugen eine sehr einfache Hello-World-Seite mit 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

Um den Build zu prüfen, können wir den Server lokal starten:

hugo serve

Dann erscheint unsere eigene “Hello World”-Seite unter http://localhost:1313

Bisher so gut. Betrachten wir nun, wie wir das in einen Container packen.

Zwei Ansätze zur Containerisierung

Technisch gesehen können wir hier zwei verschiedene Wege gehen:

  1. Den gesamten Quellcode in einen Container legen und den Build-Prozess darin ausführen
  2. Lokal bauen und nur die Ausgaben in den Container kopieren

Um unser Beispiel reproduzierbarer zu machen und weniger von lokalen Umgebungen abhängig zu sein, wählen wir für dieses Szenario Option 1. Das macht auch CI/CD-Pipelines sauberer, da die Build-Umgebung vollständig im Dockerfile definiert ist.

Das Container-Image bauen

Ein Container-Image beginnt immer mit einem Basis-Image. Dafür verwenden wir Alpine Linux, da es klein ist (etwa 5 MB) und ausreichend Werkzeuge für unser Projekt bereitstellt.

Wir verwenden ein Multi-Stage-Build, eine Technik, die von modernen Container-Build-Tools unterstützt wird und es uns ermöglicht, ein Image zum Bauen und ein anderes zum Ausführen zu verwenden. So bleibt unser finales Image klein, weil Build-Tools, die wir zur Laufzeit nicht benötigen, ausgeschlossen werden.

Phase 1: Erstellen

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

In dieser Phase:

Die gebaute Site landet in /src/public/.

Phase 2: Laufzeit

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 dieser Phase:

Das komplette Dockerfile

Hier ist das vollständige Dockerfile, das beide Phasen kombiniert:

# 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"]

Speichern Sie dies als Dockerfile im Stammverzeichnis Ihres Hugo-Projekts.

Lokal bauen und ausführen

Sie können jedes OCI-kompatible Container-Tool wie Podman oder Docker verwenden. Die Beispiele unten verwenden docker, aber podman funktioniert als Drop-in-Ersatz.

Um das Image zu bauen:

docker build -t my-website .

Um es lokal auszuführen:

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

Ihre Seite ist jetzt unter http://localhost:8080 erreichbar.

Der Parameter -p 8080:80 mappt Port 8080 auf Ihrem Rechner auf Port 80 im Container. Der Parameter --rm entfernt den Container automatisch, wenn er stoppt.

Warum dieser Ansatz gut funktioniert

Dieses Setup hat mehrere Vorteile:

  1. Kleine Image-Größe: Das finale Image enthält nur Alpine (~5MB) + lighttpd (~1MB) + Ihre HTML-Dateien. Kein Node.js, kein Ruby, keine Build-Tools, die Ihr Produktions-Image aufblähen.

  2. Reproduzierbare Builds: Die exakt gleiche Hugo-Version läuft in CI wie lokal und beseitigt das “funktioniert auf meinem Rechner”-Problem.

  3. Schneller Start: lighttpd startet in Millisekunden, was dies perfekt für Scale-to-Zero-Deployments auf DTZ macht.

  4. Sicherheit: Der Produktionscontainer hat eine minimale Angriffsfläche – nur ein statischer Dateiserver ohne dynamische Laufzeit.

Architekturüberlegungen

Wenn Sie Ihr Image auf einem Apple Silicon Mac (ARM64) oder einer anderen nicht-standardmäßigen Architektur bauen, denken Sie daran, dass Server typischerweise auf AMD64 (x86_64) laufen. Um sicherzustellen, dass Ihr Container auf DTZ (und den meisten anderen Cloud-Anbietern) korrekt läuft, sollten Sie während des Builds explizit die Zielplattform angeben.

Ändern Sie Ihren Build-Befehl zu:

docker build --platform linux/amd64 -t my-website .

Das sagt Docker, das Image für Standard-Linux-Server zu cross-kompilieren und damit die Kompatibilität unabhängig von der Maschine sicherzustellen, auf der Sie bauen.

Bereitstellung auf DownToZero

Sobald Ihr Image gebaut ist, können Sie es in ein Container-Registry pushen und auf DTZ bereitstellen. Wenn Sie unsere Container-Registry verwenden:

# 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

Erstellen Sie dann im DTZ-Dashboard einen Container-Service, der auf Ihr Image zeigt. Der Service kümmert sich automatisch um TLS-Zertifikate, Skalierung und Routing.

Für automatisierte Deployments bei jedem Commit sehen Sie sich unsere GitHub Action für nahtlose Bereitstellungen an.

Anpassung für andere statische Site-Generatoren

Dasselbe Muster funktioniert für andere Generatoren. Hier sind die wichtigsten Änderungen:

For Zola:

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

For 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

Die Laufzeit-Phase bleibt gleich – kopieren Sie einfach von /src/public (Zola) oder /src/_site (Jekyll) in das Document-Root von lighttpd.

Fazit

Das Containerisieren statischer Sites ist einfach, sobald man das Muster verstanden hat: in einer Phase bauen, in einer anderen ausliefern. Das Ergebnis ist ein winziger, schneller und sicherer Container, der perfekt für moderne Cloud-Deployments geeignet ist.

Dieser Ansatz passt gut zu unserer Philosophie bei DTZ – minimaler Ressourcenverbrauch, schnelle Cold Starts und Infrastruktur, die bei Nichtbenutzung auf Null skaliert. Eine statische Site in einem ~10MB-Container, der sofort startet, ist so effizient, wie Webhosting nur sein kann.