Statische Website auf VM hosten – mit Docker & Caddy
In diesem Beitrag dokumentiere ich die wichtigsten Schritte, mit denen ich diese statische Website auf einem Hetzner-Server (Bitte verwenden Sie meinen Empfehlungslink :-)) bereitgestellt habe.
Anforderungen
Du sollst:
- Hugo oder Jekyll) auf deine lokale Machine installieren.
- Die Syntax von Markdown kennen
- Einen Ubuntu server
Zusammenfassung
- Hugo (oder Jekyll) generiert die Seiten und kopiert sie auf meinem Server.
- Caddy dient die statischen Dateien aus.
- Docker sorgt für sauberes Deploy & Auto-HTTPS.
Ein Wort zu Caddy: Vorteile & Nutzung
Caddy ist eine leistungstärke und erweiterbare platform für Webseiten, Dienste und Apps. Caddy ist in Go programmiert und einige seiner Vorteile sind:
- Auto-HTTPS out of the box: Zertifikatsverwaltung ohne Cronjobs oder externe Hooks.
- Einfaches, lesbares Konfig-Format: Das
Caddyfileist kurz und gut verständlich. - Gute Defaults: Redirects, Kompression, Security-Header sind schnell gesetzt.
- Performant für statische Assets: Ideal für Jekyll-Seiten, Blogs, Docs, Portfolios.
- Erweiterbar: Wenn später eine dynamische Site (z. B. WordPress) dazukommt, kann Caddy per
php_fastcgi/Reverse-Proxy erweitert werden – ohne die statische Site anzufassen.
Architektur-Überblick
[Meine Lokale Maschine/CI]
Hugo-Build
│ (rsync/scp)
▼
┌──────────────────────────────────────────┐
│ Hetzner-VM (Linux) │
│ - Firewall: 80/tcp, 443/tcp erlaubt │
│ - Docker Engine │
│ - Dateipfade unter /srv/www │
│ │
│ docker-compose.yml │
│ └─ caddy (Container) │
│ - Caddyfile │
│ - /srv/static_site (ro) │
│ - Auto-HTTPS (Let's Encrypt) │
└──────────────────────────────────────────┘
▲ ▲
│ │
DNS A/AAAA -> Server-IP Browser-UserWarum habe ich diese Architektur benutzt?
Klar getrennte Rollen
- Hugo: erzeugt statische HTML/CSS/JS (Build lokal oder in CI).
- Caddy (im Docker-Container): liefert Dateien aus, holt automatisch Let’s-Encrypt-Zertifikate und
Wartungsarm & portabel – durch Docker reproduzierbar; Updates sind ein
docker compose pull && up -d.
1) Statische Seite mit Hugo bauen
Sie können eine gute Dokumentation auf der Webseite von Hugo finden. Wie schon gesagt, können Sie auch Jekyll statt Hugo für die Generierung der statischen Webseite verwenden. Ich werde auch wahrscheinlich einen Post über Jekyll schreiben.
Nach der Generierung der Seiten deploye ich den Inhalt von public/ danach auf meinem Server (siehe Schritt 5).
2) Verzeichnisstruktur auf dem Server
sudo mkdir -p /srv/www/incompletenotes/public
sudo mkdir -p /srv/www/caddypublic/enthält die fertigen statischen Dateien (aus dem vorherigen Schritt).- In
caddy/liegt die Caddy-Konfiguration DateiCaddyfile.
3) docker-compose.yml für Caddy
version: "3.9"
services:
caddy:
image: caddy:2
container_name: caddy
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- /srv/www/caddy/Caddyfile:/etc/caddy/Caddyfile:ro
- /srv/www/incompletenotes/public:/srv/static/site:ro
- caddy_data:/data
- caddy_config:/config
volumes:
caddy_data:
caddy_config:
caddy_dataspeichert u. a. Zertifikate persistent.
4) Caddyfile (Konfiguration)
www.incompletenotes.com {
redir https://incompletenotes.com{uri} permanent
}
# Statische Seite
incompletenotes.com {
root * /srv/static/site
encode zstd gzip
file_server
# Cache-Header für Assets
@assets path_regexp assets \.(?:css|js|png|jpe?g|gif|svg|ico|webp|woff2?)$
header @assets Cache-Control "public, max-age=31536000, immutable"
# Basisschutz-Header
header {
X-Content-Type-Options "nosniff"
Referrer-Policy "strict-origin-when-cross-origin"
Permissions-Policy "geolocation=(), microphone=()"
}
}Was Caddy hier automatisch übernimmt:
- Let’s-Encrypt-Zertifikate (sehr hilfreiche Option).
- HTTP→HTTPS-Redirects. (finde ich auch cool)
- Kompression (gzip/zstd) und effizientes Ausliefern statischer Dateien.
5) Deployment des Hugo-Builds
#Building Hugo site
hugo --minify
#Transferring files to server
rsync -avz --delete public/ my_user@server_ip:/srv/www/incompletenotes.com/public/--delete sorgt dafür, dass entfernte Dateien auf dem Server entfernt werden – ideal für „saubere“ Deploys.
6) DNS & Firewall (Teilweise Optional)
DNS: Ich setze
A(IPv4) und ggf.AAAA(IPv6) fürincompletenotes.com(undwww.incompletenotes.com) auf die IP(s) meiner VM.Hetzner-Cloud-Firewall (Inbound erlauben): 80/tcp, 443/tcp.
Host-Firewall (UFW):
sudo ufw allow 80/tcp sudo ufw allow 443/tcp
7) Start & Test
# auf dem Server
docker compose -f /srv/www/docker-compose.yml up -d
docker logs -f caddy # prüft Zertifikatsabruf/Start
curl -I https://incompletenotes.comWenn alles passt, solltest du HTTP/2 200 (oder HTTP/3) sehen.
Fazit
Mit Hugo -> Docker/Caddy -> VM ist eine statische Webseite in wenigen Schritten sauber live: einfaches Deployment, automatisches HTTPS, gute Performance und wenig Moving Parts. Wenn später eine dynamische Seite hinzukommt, lässt sich die Architektur problemlos erweitern – Caddy bleibt der Front-Door-Server, statische und dynamische Routen koexistieren sauber.