Statische Webseite & Webserver im Docker-Image
Veröffentlicht am 10. September 2023 • 6 Min. Lesezeit • 1.260 WörterWie packt man eine Hugo Webseite zusammen mit einem HTTP-Webserver in ein Docker Image?
Obwohl dieser Beitrag im Kontext des vorherigen
“Hugo Webseite erstellen ohne Installation”
entstanden ist, lässt sich das Prinzip auf jedes Projekt aus statischen HTML-Seiten anwenden, von Under Construction
- über Coming soon
-Landing Seiten bis eben hin zu ganzen (Hugo
)-Blog-Projekten.
Und wie bekommt man die statischen Websiten nun serviert? 🧐
Am einfachsten geht das, indem man die Webseite zusammen mit einem Webserver in ein Docker-Image steckt; aber bitte automatisiert!
Da die Webseiten bereits vorliegen, werden wir lediglich ein Skript und ein paar config-Files erstellen, um anschließend einen Docker-Image zu erstellen, das neben den eigentlichen Seiten auch einen Webserver für die Auslieferung enthält.
Das Erzeugen des Images dauert jeweils nur ein paar Minuten und läuft völlig automatisiert ab.
Sicherheitshinweis
Das erzeugte Image wird die Seiten mittels http
, auf Port 80 ausliefern. Dies entspricht nicht dem Stand der Technik und ist ohne weiteres Vorgehen nicht für den Produktivbetrieb zu empfehlen.
Das fehlende sichere Protokoll (https) lässt sich jedoch einfach in Form eines Reverse Proxys nachrüsten. In einem späteren Artikel zeige ich wie man Traefik Proxy und Let’s Encrypt als Mittler verwenden kann, um einen einfachen http-Endpunkt, wie unseren Webserver-Container, für die Verwendung von https fit zu machen.
Alle Tools sind komplett frei und OpenSource.
Ich nehme an ihr habt bereits Docker installiert. Wenn nicht gibt es da draußen gute Einführungen zum Thema Containerisierung und wie man damit am einfachsten anfängt (z.B. Docker Docs ).
Im einzelnen benötigt man
./public
Das Docker-Image wird im Wesentlichen mit dem Kommando docker image build
und Parametern erzeugt
-f
zeigt dabei auf das Konfigurations-File (dockerfile
) und-t
setzt ein optionales Docker-Tag eigener WahlUm das Image später bequem auf einen anderen Rechner verschieben zu können, exportieren wir es gleich via docker save <Docker-Tag>
als .tar.gz
File.
Wer den Artikel
“Hugo Webseite erstellen ohne Installation”
gelesen hat ahnt schon, dass ich das Skript im Hugo-Projekt-Ordner unter ./tools
speichern werde. Davon lässt sich natürlich jederzeit abweichen, solange man die wesentlichen Pfade anpasst.
#!/bin/bash
set -e
set -o pipefail
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
docker image build -f ${DIR}/dockerfile -t hugo-nginx .
docker save hugo-nginx | gzip > ${DIR}/hugo-nginx.tar.gz
Um das Docker Image bauen zu können, fehlt noch ein Bauplan für die Erstellung, der im sogenannten dockerfile
beschrieben wird. Als Grundlage wird ein minimales Image referenziert (Nginx:alpine
). Zur Konfiguration des Web-Servers wird das default-Konfigurations-File gegen ein eigenes ausgetauscht.
In das Image wird ebenfalls das final veröffentlichte Hugo-Projekt aus dem lokalen Ordner ./public
kopiert, das dort im Web-Serverbereich von Nginx (/usr/share/nginx/html
) zu liegen kommt.
# Minimales Nginx Image als Basis Image
FROM nginx:alpine
# Lösche das Nginx default Config-File
RUN rm /etc/nginx/conf.d/default.conf
# Kopiere das neue Nginx Config-File
COPY ./tools/nginx.conf /etc/nginx/nginx.conf
# kopiere das gebaute Hugo-Projekt
# in das Webverzeichnis von Nginx
COPY ./public /usr/share/nginx/html
Der Web-Server läuft zu Testzwecken auch leidlich ohne eigenes Konfigurations-File. Möchte man jedoch einen Schritt weiter gehen und das Image zusammen mit einer https
Terminierung (wie z.B. Traefik Proxy und Let’s Encrypt) ins Internet stellen, so läuft man unmittelbar in Cross-Origin Probleme. Also machen wir es gleich richtig.
Beim Kopieren des Konfigurations-Files bitte sicherstellen, dass an drei Stellen die spätere Domain eingetragen wird.!
Bei einem Hugo Projekt wird diese in config.toml
z.B. als baseURL = "https://FrankSchmidt-Bruecken.com/"
eingetragen.
worker_processes auto;
events {
worker_connections 1024;
}
http {
include mime.types;
map $http_origin $allow_origin {
default "*";
"~^https?://(frankschmidt-bruecken\.de|localhost:8080)$" "$http_origin"; # <<< ersetze Domain (kein www.)
}
map $request_method $cors_method {
default "allowed";
"OPTIONS" "preflight";
}
map $cors_method $cors_max_age {
default "";
"preflight" 3600;
}
map $cors_method $cors_allow_methods {
default "";
"preflight" "GET, POST, OPTIONS";
}
map $cors_method $cors_allow_headers {
default "";
"preflight" "Authorization,Content-Type,Accept,Origin,User-Agent,DNT,Cache-Control,X-Mx-ReqToken,Keep-Alive,X-Requested-With,If-Modified-Since";
}
map $cors_method $cors_content_length {
default $initial_content_length;
"preflight" 0;
}
map $cors_method $cors_content_type {
default $initial_content_type;
"preflight" "text/plain charset=UTF-8";
}
server {
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_types text/plain text/css text/xml application/json application/javascript application/rss+xml application/atom+xml image/svg+xml;
listen 80;
#listen [::]:80;
server_name frankschmidt-bruecken.com; # <<< ersetze Domain (kein www.)
#access_log /var/log/nginx/host.access.log main;
add_header Access-Control-Allow-Origin $allow_origin;
add_header Access-Control-Allow-Credentials 'true';
add_header Access-Control-Max-Age $cors_max_age;
add_header Access-Control-Allow-Methods $cors_allow_methods;
add_header Access-Control-Allow-Headers $cors_allow_headers;
set $initial_content_length $sent_http_content_length;
add_header 'Content-Length' "";
add_header 'Content-Length' $cors_content_length;
set $initial_content_type $sent_http_content_type;
add_header Content-Type "";
add_header Content-Type $cors_content_type;
if ($request_method = 'OPTIONS') {
return 204;
}
location / {
add_header Access-Control-Allow-Origin https://www.frankschmidt-bruecken.com; # <<< ersetze www.Domain
add_header Cache-Control "public, max-age=3600";
root /usr/share/nginx/html;
index index.html index.htm;
}
error_page 404 /404.html;
# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}
}
Nachdem das Skript einmalig ausführbar gemacht wurde (chmod +x
),
chmod +x ./tools/erstelle-docker-image.sh
lässt sich das finale Image nun erzeugen.
./tools/erstelle-docker-image.sh
Wenn alles richtig gelaufen ist, befindet sich im Ordner ./tools
das erzeugte Docker-Image (/hugo-nginx.tar.gz
) unseres Projektes.
Für das lokale Testen des erzeugten Images ist es wichtig, dass vor der Erzeugung des Images im Hugo File config.toml
der Parameter baseURL
baseURL = "http://localhost/"
gesetzt wird. Steht hier der produktive Domain Name, so lassen sich die statischen Files nicht lokal testen, da die Links auf die produktive Domain zeigen würden.
#!/bin/bash
set -e
set -o pipefail
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
IMAGE="hugo-nginx"
echo "Der Web Server steht unter http://localhost/ zur Verfügung"
docker run --rm \
-p 80:80 \
${IMAGE}
chmod +x ./tools/test-image-lokal.sh
Da der Container jetzt auf Port 80 lauscht, startet man den lokalen Browser einfach mit http://localhost/
.
Der Container kann wieder mit CTRL+C beendet werden.
Oft möchte man nach der lokalen Erstellung das finale Image auf einen anderen Server kopieren. Hier ein Beispiel wie das per ssh
gehen kann. Ich habe gerne den Namen des Zielsystems bereits im Skriptnamen ersichtlich, um Missverständnisse zu vermeiden.
Innerhalb des Skriptes müssen noch die Variablen an die eigenen Bedürfnisse angepasst werden: Eine Beschreibung des Images, der Name des Image Files, die Adresse des Zielsystems, der Benutzername für den SSH-Transfer sowie der Ordner in dem das Image dort abgelegt werden soll.
Bei der Ausführung wird dann das Passwort erfragt (es sei denn man hat den lokalen ssh-Schlüssel auf das Zielsystem übertagen).
#!/bin/bash
set -e
set -o pipefail
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
IMAGEBESCHREIBUNG="MeinHugoBlog"
IMAGEFILE="${DIR}/hugo-nginx.tar.gz"
ZIELSYSTEM="meine-domain.de oder IP"
BENUTZERNAME="fritzchen"
ZIELORDNER="/srv/dockerimages/
echo ">>> Kopiere ${IMAGEBESCHREIBUNG} nach ${ZIELSYSTEM}"
scp ${DIR}/tools/${IMAGEFILE} ${BENUTZERNAME}@${ZIELSYSTEM}:${ZIELORDNER}
echo "\n>>>Fertig"
echo ">>> Das Image wurde auf ${ZIELSYSTEM} im Ordner ${ZIELORDNER} abgelegt."
Anschließend loggt man sich auf dem Zielsystem ein und startet einen Container des Images oder startet einen vorhandenen neu. Wie das zu tun ist hängt im Wesentlichen davon ab, welche Art von Reverse Proxy dort verwendet wird (siehe nächste Schritte).
In wenigen Schritten lassen sich statische Webseiten zusammen mit einem Webserver in ein Docker Image packen und auf das Zielsystem kopieren.
Aus meiner Sicht ist es immer hilfreich zu Beginn eines Projektes ein wenig Zeit in die Erstellung von Helferlein in Form von Skripten zu investieren. Diese nehmen einem nicht nur immer wiederkehrende und oft stupide Arbeiten ab, sonder beugen vor allem Leichtsinnsfehler vor.
Um nun das Webprojekt sicher online bringen zu können, müssen wir noch dafür sorgen, dass die Seiten sicher per HTTPS ausgeliefert werden können. Dazu gibt es mehrere Möglichkeiten, z.B.