Webpage im Homelab
23. Nov. 2024
Webseite im Homelab
Zwar gibt es eine Vielzahl von Anbietern, mit denen man seine eigene Webseite ins Netz stellen kann. Die dafür benötigten Bestandteile (Domäne, Webserver und die Verknüpfung der beiden) werden dann vom Anbieter verwaltet und konfiguriert. Das erspart Zeit, bedeutet aber auch, dass alle Daten auf fremder Hardware liegen.
In diesem Beitrag erkläre ich, wie man mit Docker Containern seinen eigenen Webserver betreibt und über Cloudflare online stellt. Um sicherzustellen, dass die Webseite dauerhaft online ist, empfiehlt es sich, die nachfolgenden Schritte auf einem Heim-Server (also einen dauerhaft laufenden PC) auszuführen. Das kann ein so sparsamer Computer wie der Raspberry Pi oder ein vollwertiger Rack-Server mit Intel- oder AMD-CPU sein.
Als Testzweck kann der Webserver auch auf einem normalen Computer eingerichtet werden. Doch steht die Webseite dann nicht mehr zur Verfügung, sobald der Computer vom Internet getrennt wird.
Docker
Die Einrichtung von Docker ist recht selbsterklärend. Wie man Docker Desktop oder (nur) Docker Engine installiert wird unter diesen zwei Verweisen beschrieben.
Unter macOS kann man zudem auch Homebrew verwenden, um Docker Desktop zu installieren:
brew install --cask docker
Sobald Docker gestartet ist, steht alles bereit, um einen Webserver einzurichten.
Webserver Container
Es gibt verschiedene Server die eine Webseite bereitstellen können. Server wie Ngnix oder Apache sind hierbei bekannt und weit verbreitet. Aber um lediglich eine statische Webseite bereitzustellen, sind diese Server - wie man im Fachjargon sagen würde - overkill. Daher verwende ich hier einen Server namens httpd im Container busybox - der wohlmöglich kleinste Server für statische Webseiten.
Für Testzwecke sollte man einen leeren Ordner erstellen, und darin eine Datei names index.html
speichern, die folgenden Inhalt hat:
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Test</title>
</head>
<body>
<h1>Hallo, Welt!</h1>
</body>
</html>
Nun kann der Container mit dem Befehl docker
wie folgt ausgeführt werden:
docker run \
-d \
--rm \
-p 8080:80 \
-v /path/to/folder:/www \
busybox \
/bin/sh -c 'cd /www; busybox httpd -f -v -p 80'
Der Befehl ist in verschiedene Zeilen aufgeteilt, sodass er einfacher erklärt werden kann:
docker run
ist der Grundbefehl mit dem ein Docker Container gestartet wird.-d
gibt an, dass der Container losgelöst als Hintergrundprozess (bzw. im detached mode) gestartet werden soll.--rm
gibt an, dass der Container wieder gelöscht werden soll, wenn er angehalten wurde.-p 8080:80
gibt an, dass der Port 8080 des Hosts (der den Docker Container ausführt) mit dem Port 80 im Container (also dem Webserver) verbunden werden soll.-v /path/to/folder:/www
gibt an, dass der Pfad zum Ordner, wo die Dateiindex.html
gespeichert ist, mit dem Verzeichnis/www
als Volumen in den Container eingebunden werden soll. Man kann auch den derzeitigen Arbeitsordner mittels$PWD
eingeben z.B. wenn mit der Befehlseingabe zum besagten Ordner gewechselt wurde.busybox
ist der Name des Docker Images, das als Container gestartet werden soll./bin/sh -c 'cd /www; busybox httpd -f -v -p 80'
ist das Start-Argument, das dem Container übergeben wird. Es ist etwas verschachtelt aber an sich nicht komplex:/bin/sh
started eine Shell-Umgebung.-c
gibt an, dass der nachfolgende Code (in' ... '
) von der Shell-Umgebung ausgeführt werden soll.- Der Code
'cd /www; busybox httpd -f -v -p 80'
wechselt zuerst mitcd
in den Ordner/www
und dann startet er mitbusybox httpd -f -v -p 80
den Webserver auf Port 80.
Unter der Adresse http://127.0.0.1:8080/
sollte nun das bekannte Hallo, Welt! im Browser angezeigt werden.
Soweit so gut. Nur ist dieser Webserver noch nicht mit dem Internet verbunden. Hierfür muss eine Domäne und Cloudflare eingerichtet werden.
Cloudflare Container
Um auf den Webserver über das Internet zugreifen zu können braucht man zuerst ein Konto bei Cloudflare. Das anzulegen ist kostenlos. Nun muss man seine Domäne bei Coudflare registrieren. Hierfür gibt es auch eine Kostenlose Option:
Nun müssen die Nameserver beim Registrar (wie Ionos, Godaddy etc.) aktualisiert werden, um die Dienste von Cloudflare für die Domain zu aktivieren. Diese Nameserver haben in der Regel die folgende Namensstruktur: *.ns.cloudflare.com
. Sobald die Änderung beim Registrar angewendet wurden, kann es losgehen.
Im Zero Trust Menü von Cloudflare, kann man im Reiter Networks einen Cloudflared Tunnel einrichten. Klickt man sich durch das Setup durch, erhält man am Ende eine Auswahl, wie man den Tunnel für verschiedene Umgebungen einrichten kann. Für Docker wird dann ein Befehl wie folgt angezeigt:
docker run cloudflare/cloudflared:latest tunnel --no-autoupdate run --token xxxxxx....
Der Token wird automatisch generiert und ist für jeden Tunnel einzigartig.
Damit der Container losgelöst ausgeführt wird und nach seiner Beendigung gelöscht wird, empfiehlt es sich, die Zusätze -d
und --rm
hinzuzufügen. Der Befehl ist nachfolgend auf mehrere Zeilen aufgeteilt, um ihn besser lesbar zu machen, und der Token wird nicht als Teil des Startbefehls übergeben, sondern als Umgebungsvariable (was angeblich sicherer ist):
docker run \
-d \
--rm \
cloudflare/cloudflared:latest \
-e TUNNEL_TOKEN=xxxxxx.... \
tunnel --no-autoupdate run
Führt man den Befehl so aus, wird ein Container gestartet, der sich mit Cloudflare verbindet und einen verschlüsselten Tunnel erstellt. Der Tunnel ermöglicht es, dass Cloudflare Webanfragen an die registrierte Domäne über den Tunnel an einen bestimmten Endpunkt im Heimnetz weiterleitet. Somit kann über Cloudflare gezielt mit Servern im Heimnetz kommuniziert werden. Dafür kann man nun öffentliche Hostnamen (Domänen und Sub-Domänen) im Menü des Tunnels registrieren und sie dort mit der Adresse des Servers verbinden.
Hat der Computer, auf dem der zuvor eingerichtete Webserver im Container läuft z.B. die IP Adresse 192.168.1.2
, so kann man bei Cloudflare den Dienst über http://192.168.1.2:8080
mit einem Hostnamen verbinden.
Das hilf aber nur begrenzt, wenn sich die IP Adresse des Computers (z.B. aufgrund einer neuen Zuweisung über DHCL) ändert. Da der Cloudflare Container und der Webserver Container nicht unmittelbar miteinander in Verbindung stehen, müsste man dann die IP Adresse im Tunnel aktualisieren. Um dies zu vermeiden, können die zwei Container im selben Netzwerk gestartet werden.
Docker Netzwerk
Um Container in einem bestimmten Netzwerk zu starten, muss man zuerst ein Netzwerk definieren. Ich nenne es hier der Einfachheit halber cloudflare
:
docker network create cloudflare
Man kann nun prüfen, dass das Netzwerk erstellt wurde:
docker network inspect cloudflare
Und sollte dann ein Ergebnis wie folgt erhalten:
[
{
"Name": "cloudflare",
"Id": "xxxxx....",
"Created": "2024-11-23T18:53:29.439745922Z",
"Scope": "local",
"Driver": "bridge",
"EnableIPv6": false,
"IPAM": {
"Driver": "default",
"Options": {},
"Config": [
{
"Subnet": "172.18.0.0/16",
"Gateway": "172.18.0.1"
}
]
},
"Internal": false,
"Attachable": false,
"Ingress": false,
"ConfigFrom": {
"Network": ""
},
"ConfigOnly": false,
"Containers": {},
"Options": {},
"Labels": {}
}
]
Nun muss man beide Container anhalten und mit dem Zusatz --network=cloudflare
neu starten. Um die Container zu beenden braucht man ihre IDs, die man wie folgt erhalten kann:
docker container ls
Wodurch die folgende Ausgebe zu sehen sein sollte:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
aaa... cloudflare/cloudflared:latest "cloudflared --no-au…" 5 minutes ago Up 5 minutes brave_greider
bbb... busybox "/bin/sh -c 'cd /www…" 5 minutes ago Up 5 minutes 0.0.0.0:8080->80/tcp wizardly_dhawan
Mit dem folgenden Befehl kann dann jeder Container angehalten werden (die ID muss entsprechend geändert werden):
docker container stopp <ID>
Beide Container können nun mit den folgenden Befehlen neugestartet werden, allerdings so, dass sie in dem Netzwerk cloudflare
gestartet werden:
docker run \
--network=cloudflare \
-d \
--rm \
-e TUNNEL_TOKEN=xxxxxx.... \
cloudflare/cloudflared:latest \
tunnel --no-autoupdate run
Wenn nun der Webserver gestartet wird, sollte ihm auch ein eindeutiger Name zugewiesen werden, sodass er im Netzwerk cloudflare
einfacher zu identifizieren ist:
docker run \
--name=busybox-httpd \
--network=cloudflare \
-d \
--rm \
-p 8080:80 \
-v /path/to/folder:/www \
busybox \
/bin/sh -c 'cd /www; busybox httpd -f -v -p 80'
Nun kann man anstelle der IP Adresse im Menü des Tunnels bei Cloudflare einfach den Dienst wie folgt angeben:
http://busybox-httpd:80
Denn der da sowohl der Cloudflare Container, als auch der Webserver Container im selben Netzwerk laufen, kann der eine Container den anderen sehen und über dessen Namen mit ihm kommunizieren. Das Bedeutet, dass der Hostname http://busybox-httpd:80
nie das Netzwerk der zwei Container verlässt, während vorher die Anfrage einen kleinen Umweg über den Host machen musste. Demnach kann man auch den Webserver Container starten, ohne den Port mit -p 8080:80
im Heimnetz zu veröffentlichen. Lässt man diesen Zusatz vom Startbefehl aus, so ist der Webserver nur noch über den Cloudflare Container, und somit nur noch über den damit verbundenen öffentlichen Hostnamen erreichbar. Doch wird dadurch die Webentwicklung etwas umständlich, weshalb ich oft meine Container über einen lokalen Port erreichbar mache.
Und somit kann man einen Webserver lokal betreiben, der über das Internet erreichbar ist, womit der Beitrag eigentlich abgeschlossen ist.
Allerdings wirkt das Erstellen eines Netzwerks und der koordinierte Start von zwei Containern etwas umständlich. Um dies besser zu Koordinieren gibt es ein Tool namens Docker Compose
Docker Compose
Mit dem Befehl docker compose
werden Informationen aus einer YAML Datei namens compose.yaml
ausgelesen und dementsprechend mehrere Container mit bestimmten Konfigurationen gestartet. Da die Datei Angaben zu beiden Containern und dem Netzwerk enthält, ist es einfacher, die Container aufeinander abzustimmen. Der Inhalt der Datei sieht dann wie folgt aus:
services:
webserver:
container_name: busybox-httpd
image: busybox
restart: unless-stopped
working_dir: /www
command: httpd -f -v -p 80
volumes:
- ./:/www
tunnel:
container_name: cloudflared-tunnel
image: cloudflare/cloudflared
restart: unless-stopped
command: tunnel --no-autoupdate run
environment:
- TUNNEL_TOKEN=xxxxxx....
networks:
default:
name: cloudflare
Hierbei werden zwei Dienste (webserver
und tunnel
) definiert und ein Netzwerk (cloudflare
), das beide miteinander verbindet. Die Angaben zu den Diensten sind entsprechend der Container von vorhin, nur anstelle von Flags werden die Container mit Werten im YAML Format konfiguriert.
Die Container werden dann wie folgt gestartet:
docker compose up
Man sieht nun eine Ausgabe die folgendes enthalten sollte:
...
[+] Running 2/2
✔ Container cloudflared-tunnel Created 0.2s
✔ Container busybox-httpd Created 0.2s
...
Das bedeutet, dass beide Container gestartet wurden. Mit strg + c werden die Container gestoppt:
....
[+] Stopping 2/2
✔ Container cloudflared-tunnel Stopped 0.7s
✔ Container busybox-httpd Stopped 10.1s
...
Möchte man die Ausgabe nicht sehen, können die Container mit dem Zusatz -d
losgelöst gestartet werden:
docker compose up -d
Da die Tastenkombination nun nicht mehr funktioniert, können sie mit dem folgendem Befehl gestoppt werden:
docker compose stop
Und somit sind wir wirklich beim Ende des Beitrags angekommen.
Mögliche Fehlerbehebung
Es kann vorkommen, dass bei einem Neustart der Container mittels Docker Compose einer der Container sich nicht mit dem Netzwerk verbindet. Hierfür kann man versuchen, die gestoppten Container zu löschen bevor man sie wieder startet:
docker compose rm
Alternativ kann man das Netzwerk wie gehabt erstellen und dann in der YAML Datei angeben, dass ein externes Netzwerk benutzt werden soll:
...
networks:
default:
name: cloudflare
external: true