Ubuntu Homeserver einrichten (Intel-Nuc)
Ausführliche Schritt-für-Schritt Anleitung für die Einrichtung eines Ubuntu-Homeservers mit Docker und Docker-Compose.
Inhaltsverzeichnis
In diesem Blogpost möchte ich einen Homeserver mit Ubuntu und Docker einrichten. Dieser soll das erste Gerät hinter dem Router werden, der sich unter anderem um externe Anfragen kümmert.
Voraussetzungen
- Server, auf dem Ubuntu installiert werden kann
- Monitor und Tastatur für die Installation
- Internetverbindung (bestenfalls ohne DS-Lite)
- eigene Domain
- USB-Stick oder SD-Karte mit mindestens 2 GB freiem Speicher
- Computer (Client), um den Server einzurichten (zum Beispiel ein Windows-PC)
- bestenfalls Router, der sich um das DDNS und VPN kümmert (zum Beispiel Fritz!Box 7590) (Affiliate-Link)
Ubuntu Server
Die Voraussetzungen für diese Anleitung ist ein Computer auf dem Ubuntu installiert werden könnte. Es kann prinzipiell fast jeder Desktop-PC oder Laptop sein. Ich selbst nutze einen
Intel NUC
(Amazon Affiliate-Link).
Intel Nuc
Grundsätzlich könnte man (wie auch ich in der Vergangenheit) auch einen Raspberry Pi dafür nehmen, jedoch habe ich das Gefühl, dass ein Raspberry Pi als “Hauptserver” nicht ideal ist. Ein Intel Nuc bietet demgegenüber einige Vorteile (und leider auch Nachteile).
Selbst der Nuc mit einem J4005-Prozessor ist leistungsstärker als ein Raspberry Pi 4. Wichtig ist dabei auch, dass es keine ARM-CPU wie beim Raspberry ist, wodurch einige Kompatibilitätsprobleme wegfallen.
Ein SATA Anschluss ist vorhanden, beim Raspberry Pi muss man diesen nachrüsten oder kann nur einen USB-Anschluss verwenden. Alternativ auch microSD Karten, die gefühlt sehr schnell darin kaputtgehen. Wenn man möchte, kann man in einen Intel Nuc theoretisch mehr RAM einbauen.
Ein Nachteil ist die Lautstärke, der Lüfter läuft ständig. Der Raspberry Pi ist standardmäßig passiv gekühlt.
Die Anschaffungskosten sind höher. Der Preis für ein Intel Nuc mit einem J4005, 8 GB RAM und einer 512 GB SSD liegt insgesamt bei ungefähr 240 €.
Hardware | Preis (ca.) |
---|---|
Intel NUC* | 135 € |
Gskill 2x4GB RAM* | 45 € |
Crucial 500GB SSD* | 60 € |
Der Stromverbrauch ist höher, jedoch nur geringfügig. Im Idle benötigt der Intel Nuc mit dem J4005 Prozessor ca. 4,5 Watt. Der Raspberry Pi 4 braucht ca. 3,5 Watt. Bei mir fungiert der Nuc als Proxy Manager, DNS-Server, außerdem laufen mehrere andere Dienste da drauf. Mit dem Brennenstuhl Energiemessgerät (Amazon Affiliate-Link) habe ich den Stromverbrauch gemessen und komme durchschnittlich auf einen Verbrauch von ca. 10-11 Watt. Mit TLP lässt er sich noch etwas reduzieren.
Stromkostenrechner
Hier Daten eintragen, um die Stromkosten zu berechnen:
Ergebnis
Andere Optionen
Natürlich gibt es weitaus mehr Optionen, die man nutzen könnte. Zum Beispiel ist es möglich einen nicht mehr genutzten Computer zum Server umzufunktionieren oder auch sich selbst einen Server zusammenzustellen.
Bei einem älteren Computer hat man eventuell das Problem, dass er nicht energieeffizient arbeitet. Beim Selbstbau hat man definitiv die höchsten Anpassungsmöglichkeiten und die potenziell höchste Leistung. Dies kann jedoch schnell sehr teuer werden. Interessant sind jedenfalls auch (ggf. gebrauchte) PCs mit einer nicht ganz so stromhungrigen CPU. Zum Beispiel die kleinen ThinkCentre von Lenovo Amazon Alliliate Link .
An dieser Stelle möchte ich nicht vergessen, auf die Quellen zu verweisen, von denen ich einige Informationen übernehme. Das ist zum einen insbesondere das “Silverbox” Repository von ovk. Außerdem auch das Docker-Traefik Repository von htpcBeginner.
Installation
Nachdem die Wahl auf ein Gerät gefallen ist, muss das Betriebsystem installiert werden.
Vorbereitung
Bei einem NUC müssen ggf. noch die Einzelteile eingebaut und das BIOS geupdated werden.
Bei einem Nuc müssen die Festplatte und der Arbeitsspeicher in das Gehäuse gepackt werden.
Anschließend kann der Nuc mit einem Monitor und Tastatur verbunden werden.
Beim ersten Start sollte nicht das Betriebssystem, sondern ein BIOS-Update eingespielt werden, wenn eins vorhanden ist. Dazu die BIOS-Dateien auf den USB-Stick packen.
Anschließend die Anleitung vom Hersteller befolgen (im Grunde BIOS über F2 aufrufen und dort zum BIOS-Update navigieren).
Wenn man sich sowieso schon im BIOS befindet, kann man auch die Dinge ausschalten, die man nicht nutzen möchte, z. B. das WLAN.
Als Betriebssystem möchte ich Ubuntu 20.04 Server installieren. Ubuntu 20.04 (Focal Fossa) wird noch bis 2025 mit Updates versorgt. Im Gegensatz zur Desktop-Variante hat Ubuntu Server keine grafische Oberfläche.
Um einen bootbaren USB-Stick zu erstellen, muss die ISO-Datei mithilfe von Rufus auf einen USB-Stick geschrieben werden.
Mit diesem USB-Stick kann dann der Server booten, ggf. muss beim Start das Boot-Menü aufgerufen werden (beim Nuc lässt sich das BIOS standardmäßig mit F2 öffnen).
Anschließend kann Ubuntu installiert werden.
Eventuell fragt der Installer, ob man eine aktuellere Version herunterladen möchte, dies sollte bejaht werden.
Feste IP-Adressenzuweisung
Bei der Interneteinstellung kann von der automatischen IP-Adressen Vergabe auf manuell gewechselt werden. Für mich ist es bei IPv4 wichtig, da ich IPv6 nicht nutzen werde. Das Subnet ist für gewöhnlich ein Block aus mehreren Netzwerkadressen. Standardmäßig ist es 192.168.0.0/24, 192.168.1.0/24 oder 192.168.178.0/24. Die 24 steht dafür, dass es sich beispielsweise um den Bereich 192.168.0.1 - 192.168.0.254 handelt. Das Gateway ist meistens die IP-Adresse des Modems.
Unter Windows kann man das Standard-Gateway und die eigene IP-Adresse herausfinden, indem man das Terminal aufruft und ipconfig
eingibt. Bei macOS geht es mit route get default | grep gateway
.
Gibt man manuell eine Adresse ein, ist es vorteilhaft eine zu wählen, die sich außerhalb des DHCP-Bereichs des Routers (oder anderen DHCP-Servers) befindet. Eventuell muss dafür der DHCP-Bereich verkleinert werden.
Bei einer Fritz!Box befindet sich diese Einstellungsmöglichkeit unter Netzwerk > Netzwerkeinstellungen > IPv4-Einstellungen
Volumen-Aufteilung
Im nächsten Schritt muss die Festplatte partitioniert werden. Auch hier hat man wieder mehrere Möglichkeiten. Ich wähle zunächst aus, dass ich ein custom layout möchte.
Manche Computer haben Schwierigkeiten, Boot-Dateien zu erkennen, die nicht am Anfang der Festplatte gespeichert sind. Aus diesem Grund ist es manchmal notwendig, eine separate /boot-Partition am Anfang der Festplatte zu erstellen. Deshalb möchte ich eine 1 GB große ext4 Partition für das Verzeichnis /boot haben.
Anschließend nutze ich beinahe den gesamten freien Speicherplatz für ein Ubuntu - LVM. Dieser Teil des Speichers kann auf Wunsch verschlüsselt werden. Leider ist mir nicht richtig bewusst, ob eine Verschlüsselung zusätzliche Sicherheit (außer bei einem analogen Diebstahl) bietet.
Auf diesem Volumen-Manager füge ich zwei logische Volumen hinzu. Der erste Teil dient als Swap. Auf dem zweiten werden die gewöhnlichen Daten gespeichert.
Die passende Speichergröße hängt von mehreren Faktoren ab und kann nicht im Allgemeinen angegeben werden. Ubuntu Users
Für Ubuntu ist folgende Swap-Größe empfohlen:
Empfohlene SWAP-Größe:
Mindestens: 0 GB | Maximal: 0 GB
Der einzige Nachteil von mehr Swap ist es, dass der Speicherplatz dafür reserviert werden muss. Zudem ist zu berücksichtigen, dass der Zugriff auf die SSD langsamer erfolgt im Vergleich zum Arbeitsspeicher und die SSD-Zellen eine begrenzte Lebensdauer haben. Jeder Schreib-Zyklus nutzt eine Speicherzelle ab, und irgendwann funktioniert sie nicht mehr.
Braucht man eine Swap-Partition, die größer ist als das Doppelte der RAM-Größe, sollte man definitiv über ein Upgrade des Arbeitsspeichers nachdenken.
Den restlichen Platz nutze ich als gewöhnliche ext4 Partition, die unter / gebootet werden soll.
Im Gegensatz zu meinem GIF ist es nicht erforderlich oder sinnvoll, den gesamten Platz der “Volume Group” zuzuweisen, um leichter auf spätere Veränderungen reagieren zu können. Einige GB können auch nicht zugewiesen werden.
Abschluss
Ich nutze die Option, dass OpenSSH automatisch installiert wird. Damit möchte ich später auf den Server zugreifen können.
Die zusätzlichen Dienste interessieren mich nicht, da ich diese nicht benötige.
Anschließend folge ich den Anweisungen aus dem Installer. Nachdem die Installation abgeschlossen ist, muss letztlich das bootbare Speichermedium herausgenommen werden. Der Server fährt dann normal hoch und fragt nach dem Passwort für die Festplattenverschlüsselung.
Vorsicht! Das Layout der Tastatur könnte sich inzwischen geändert haben. Sind im Passwort Sonderzeichen oder die Buchstaben Y und Z enthalten, könnten sie zu diesem Zeitpunkt anders gemappt sein.
Sagt der Server, dass das Passwort falsch sei, lohnt es sich, es unter Beachtung einer Tastatur mit Qwerty-Layout nochmal zu versuchen.
Nach Eingabe des richtigen PassworTs wird die Anmeldemaske angezeigt. Wir wollen uns nicht direkt anmelden. An dieser Stelle kann zur Sicherheit im Router dem Server eine feste IP-Adresse zugewiesen werden.
Die Installation ist damit fertig.
Erste Schritte
SSH-Verbindung herstellen
Jetzt sollte es möglich sein, sich mit dem Server über SSH zu verbinden. Ich empfehle die Programme Git (inklusive Git Bash) sowie VSCode (mit der Remote - SSH Erweiterung) zu installieren (beides kostenlos). Alternativ lässt sich aber auch das gewöhnliche Terminal verwenden.
Bei Windows muss der OpenSSH-Client eventuell noch über die Systemsteuerung > Apps > Optionale Features hinzugefügt werden.
Im Terminal sollte es danach möglich sein, sich zu verbinden:
ssh benutzername@ip-adresse
Bei Visual Studio Code kann man sich mit dem Server verbinden, indem man auf das Icon unten links drückt (bei mir ist es gelb).
Danach kann man auch dort ein Terminal aufrufen.
Server updaten
Als Nächstes sollte der Server upgedatet werden.
apt update
sudo apt update
sudo apt upgrade
Jetzt sollte ein Upgrade durchgeführt werden. Eventuell kommt aber auch die Fehlermeldung, dass bestimmte URLs nicht aufgelöst werden konnten. Dieses Problem lässt sich vermutlich durch einen Wechsel des DHCP-Servers beheben:
sudo ls /etc/netplan
Der Befehl ls /etc/netplan
gibt eine Liste aller Dateien im Verzeichnis /etc/netplan
aus. Eine oder zwei Dateien sollten darin enthalten sein. Möchte man mehr Informationen sowie versteckte Dateien sehen, kann man stattdessen auch sudo ls -Al /etc/netplan
ausführen.
Im Verzeichnis sollte sich mindestens eine Datei befinden, die wollen wir anpassen.
sudo vim /etc/netplan/00-installer-config.yaml
Vim ist ein Texteditor. Um eine Datei zu verlassen, muss man :q
eingeben. Um eine Datei anzupassen, muss man mit I in den Insert-Modus wechseln. Nachdem die Bearbeitung fertig ist, verlässt man den Insert-Modus mit Esc wieder und kann mit :x
speichern und verlassen oder mit :q!
verlassen ohne zu speichern. Vorteil von Vim ist, dass es fast überall standardmäßig installiert ist.
network:
ethernets:
eth0:
dhcp4: no
addresses: [192.168.123.5/24]
gateway4: 192.168.123.1
nameservers:
addresses: [8.8.8.8]
version: 2
Der Netzwerkname (in meinem Fall “eth0”) sollte nicht verändert werden. Demgegenüber müssen aber die restlichen Adressen angepasst werden. Die Adresse des Servers ist eventuell eine andere, die “24” steht für das Subnetz. Das Gateway ist die Adresse des Routers. Beim Nameservers.addresses kann die Adresse eines DNS-Servers eingetragen werden, wie z. B. 8.8.8.8 für Google DNS oder 1.1.1.1 für Cloudflare DNS oder 176.9.93.198 für dnsforge.de. Das ist nur eine temporäre Einstellung, die wir später wieder ändern werden.
sudo netplan apply
sudo apt update
sudo apt upgrade
Diesmal müsste das Update klappen.
IPv6 deaktivieren
Obwohl mir klar ist, dass IPv6 die Zukunft ist, habe ich immer wieder damit Probleme. Bei dem letzten Server mit Unbound hat das interne Routing nicht funktioniert, bis ich IPv6 ausgeschaltet habe. Das mache ich dieses Mal gleich. Sobald ich irgendwann gezwungen bin, auf IPv4 zu verzichten, werde ich mich erneut mit IPv6 beschäftigen.
Ich deaktiviere IPv6 in meinem Router. Bei meiner Fritz!Box befindet sich die Möglichkeit dazu unter Internet > Zugangsdaten > IPv6.
Danach deaktiviere ich das auch auf meinem Server.
sudo vim /etc/default/grub
In dieser Datei ändere ich die folgenden zwei Zeilen von
GRUB_CMDLINE_LINUX_DEFAULT=""
GRUB_CMDLINE_LINUX=""
auf
GRUB_CMDLINE_LINUX_DEFAULT="ipv6.disable=1"
GRUB_CMDLINE_LINUX="ipv6.disable=1"
wende die neue Konfiguration an und starte den Server neu.
sudo update-grub
sudo reboot
Das Blöde ist, dass man wieder am Server das Entschlüsselungspassword eingeben muss, bevor man sich über SSH mit ihm verbinden kann. Das Tastaturlayout sollte dieses Mal das der Tastatur sein, weil es inzwischen nachgeladen sein sollte.
Dropbear
Ich würde gerne nicht jedes Mal eine Tastatur und Monitor anschließen müssen, wenn ich den Server neu starte, sondern ihn auch von meinem Client-PC entschlüsseln können. Dazu installiere ich das Programm Dropbear.
sudo apt install dropbear-initramfs
Während der Installation erscheinen einige Fehlermeldungen, die zu diesem Zeitpunkt irrelevant sind. Danach ändere ich den Port des Dropbear Servers:
sudo vim /etc/dropbear-initramfs/config
Die Zeile
#DROPBEAR_OPTIONS=""
muss auskommentiert (das # am Anfang der Zeile, dass es nur ein Kommentar ist) und um ein paar Optionen ergänzt werden.
DROPBEAR_OPTIONS="-I 180 -j -k -p 2222 -s"
Jetzt brauche ich einen SSH-Schlüssel zur Authentifizierung. Dazu erstelle ich auf meinem Client-PC eine neue Datei im Verzeichnis ~/.ssh
(~ ist das Home-Verzeichnis) und nenne sie zum Beispiel “nuc_dropbear” (ohne Dateiendung). Dafür müssen Dateiendungen sowie die Anzeige versteckter Ordner aktiviert werden.
Nun rufe ich ein neues Terminal auf.
ssh-keygen -t rsa -f ~/.ssh/nuc_dropbear
Bei der Frage, ob die Datei überschrieben werden soll, antworte ich mit ja. Unter Windows sagt das Terminal eventuell, dass es diese Datei nicht gibt. In diesem Fall stattdessen den kompletten Pfad eingeben:
ssh-keygen -t rsa -f C:\Users\benutzername\.ssh\nuc_dropbear
Die Datei mit der .pub-Endung öffne ich (mit einem Texteditor, nicht mit Microsoft Publisher) und kopiere den Inhalt.
Zurück auf dem Server:
sudo vim /etc/dropbear-initramfs/authorized_keys
In diese neue Datei füge ich den Inhalt der Datei ...\nuc_dropbear.pub
ein.
Danach update ich den initrams.
sudo update-initramfs -u
Jetzt erfolgt der Moment, in dem die neue Konfiguration getestet werden kann.
sudo reboot
Diesmal verbinden wir uns mit dem Server aber nicht über Port 22, sondern über Port 2222. Außerdem mit dem Benutzer root und nicht unserem gewöhnlichen Benutzerkonto.
ssh -o "HostKeyAlgorithms ssh-rsa" -i ~/.ssh/nuc_dropbear -p 2222 root@ip-adresse
Nachdem die Verbindung dann hergestellt wurde:
cryptroot-unlock
Nachdem das Passwort zur Entschlüsselung eingegeben wurde, wird die Verbindung getrennt. Anschließend kann man sich wieder auf gewohntem Weg anmelden.
ICMP Weiterleitungen unterbinden
Als Nächstes deaktiviere ich ICMP Weiterleitungen aus Sicherheitsgründen.
sudo vim /etc/sysctl.conf
Dort kommentiere ich die folgende Zeile aus:
net.ipv4.conf.all.accept_redirects = 0
net.ipv4.conf.all.send_redirects = 0
net.ipv4.conf.all.accept_source_route = 0
Deinstallation von Snapd
Ich deinstalliere Snapd
sudo apt autoremove --purge snapd
TRIM zur verschlüsselten Partition hinzufügen
Ich füge die TRIM-Funktion zur verschlüsselten Partition hinzu. Dies ist jedoch mit Sicherheitsrisiken verbunden.
Wenn es zwingend erforderlich ist, dass Informationen über ungenutzte Sektoren für Angreifer nicht verfügbar sein dürfen, muss TRIM immer deaktiviert werden. Quelle
sudo vim /etc/crypttab
Hinter das Wort “luks” füge ich “,discard” ein.
Auslagerung von temporären Dateien
Ich verschiebe den Ordner /tmp
von der SSD in den RAM. Das bringt Vorteile, weil RAM schneller ist und die SSD nicht so stark durch viele kleine Schreibvorgänge belastet wird. Man sollte auch die Nachteile kennen, der RAM kann überlaufen, die Dateien im Verzeichnis verschwinden beim Neustart und es gibt ein Sicherheitsrisiko bei Mehrbenutzersystemen, da die temporären Dateien ggf. durch einen anderen Nutzer ausgelesen werden könnten. Näheres zur Auslagerung lässt sich in diesem Wiki nachlesen.
sudo vim /etc/fstab
Ich füge eine neue Zeile hinzu:
tmpfs /tmp tmpfs defaults,noatime,nosuid,nodev,mode=1777,size=2G 0 0
Mit dieser Einstellung darf das Verzeichnis höchstens 2 GB groß werden. Hat man weniger als 8 GB RAM eingebaut, sind 2 GB eher zu viel.
Zeit, den Server wieder neu zu starten.
sudo reboot
SSH-Verbindung ändern
Ich möchte einen Schlüssel für die gewöhnliche Anmeldung erstellen, wie auch bei Dropbear. Dazu öffne ich wieder ein neues Terminal auf meinem Client-Rechner.
ssh-keygen -t rsa -f ~/.ssh/nuc
Den nuc.pub Inhalt kopiere ich und füge ihn beim Server ein.
cd ~
mkdir .ssh
sudo vim ~/.ssh/authorized_keys
Mit cd
wechsle ich das Verzeichnis auf ~. Mit mkdir
erstelle ich ein neues Verzeichnis “.ssh”. In die neue Datei füge ich den kopierten Inhalt ein.
Bei der Nutzung von VSCode passe ich die SSH-Konfigurationsdatei an. Aufgerufen wird sie auch über das SSH-Menü.
Host Nuc
HostName ip-adresse des Servers
IdentityFile ~/.ssh/nuc
User benutzername
[...]
Jetzt sollte ich mich ohne Passwort anmelden können.
Sollte das nicht klappen, kann das kopieren auch mit cat
probieren. Dazu zunächst den Ordner auf dem Server wieder löschen.
sudo rm -r ~/.ssh
Dann auf dem Client Compouter ein Terminal starten.
cat ~/.ssh/nuc.pub | ssh benutzer@ip-adresse "mkdir -p ~/.ssh && touch ~/.ssh/authorized_keys && chmod -R go= ~/.ssh && cat >> ~/.ssh/authorized_keys"
Nun sollten noch die SSH-Einstellungen des Servers angepasst werden.
sudo vim /etc/ssh/sshd_config
Ich ändere einige Einstellungen:
# $OpenBSD: sshd_config,v 1.103 2018/04/09 20:41:22 tj Exp $
# This is the sshd server system-wide configuration file. See
# sshd_config(5) for more information.
# This sshd was compiled with PATH=/usr/bin:/bin:/usr/sbin:/sbin
# The strategy used for options in the default sshd_config shipped with
# OpenSSH is to specify options with their default value where
# possible, but leave them commented. Uncommented options override the
# default value.
Include /etc/ssh/sshd_config.d/*.conf
#Port 22
AddressFamily any
#ListenAddress 0.0.0.0
#ListenAddress ::
HostKey /etc/ssh/ssh_host_rsa_key
#HostKey /etc/ssh/ssh_host_ecdsa_key
HostKey /etc/ssh/ssh_host_ed25519_key
# Ciphers and keying
#RekeyLimit default none
# Logging
#SyslogFacility AUTH
#LogLevel INFO
# Authentication:
LoginGraceTime 1m
PermitRootLogin no
#StrictModes yes
MaxAuthTries 4
MaxSessions 5
PubkeyAuthentication yes
# Expect .ssh/authorized_keys2 to be disregarded by default in future.
#AuthorizedKeysFile .ssh/authorized_keys .ssh/authorized_keys2
#AuthorizedPrincipalsFile none
#AuthorizedKeysCommand none
#AuthorizedKeysCommandUser nobody
# For this to work you will also need host keys in /etc/ssh/ssh_known_hosts
HostbasedAuthentication no
# Change to yes if you don't trust ~/.ssh/known_hosts for
# HostbasedAuthentication
#IgnoreUserKnownHosts no
# Don't read the user's ~/.rhosts and ~/.shosts files
IgnoreRhosts yes
# To disable tunneled clear text passwords, change to no here!
PasswordAuthentication no
PermitEmptyPasswords no
# Change to yes to enable challenge-response passwords (beware issues with
# some PAM modules and threads)
ChallengeResponseAuthentication no
# Kerberos options
#KerberosAuthentication no
#KerberosOrLocalPasswd yes
#KerberosTicketCleanup yes
#KerberosGetAFSToken no
# GSSAPI options
#GSSAPIAuthentication no
#GSSAPICleanupCredentials yes
#GSSAPIStrictAcceptorCheck yes
#GSSAPIKeyExchange no
# Set this to 'yes' to enable PAM authentication, account processing,
# and session processing. If this is enabled, PAM authentication will
# be allowed through the ChallengeResponseAuthentication and
# PasswordAuthentication. Depending on your PAM configuration,
# PAM authentication via ChallengeResponseAuthentication may bypass
# the setting of "PermitRootLogin without-password".
# If you just want the PAM account and session checks to run without
# PAM authentication, then enable this but set PasswordAuthentication
# and ChallengeResponseAuthentication to 'no'.
UsePAM yes
AllowAgentForwarding no
AllowTcpForwarding local
#GatewayPorts no
X11Forwarding no
#X11DisplayOffset 10
#X11UseLocalhost yes
#PermitTTY yes
PrintMotd no
#PrintLastLog yes
TCPKeepAlive no
#PermitUserEnvironment no
#Compression delayed
ClientAliveInterval 60
ClientAliveCountMax 2
#UseDNS no
#PidFile /var/run/sshd.pid
#MaxStartups 10:30:100
#PermitTunnel no
#ChrootDirectory none
#VersionAddendum none
# no default banner path
Banner none
# Allow client to pass locale environment variables
AcceptEnv LANG LC_*
# override default of no subsystems
Subsystem sftp /usr/lib/openssh/sftp-server
# Example of overriding settings on a per-user basis
#Match User anoncvs
# X11Forwarding no
# AllowTcpForwarding no
# PermitTTY no
# ForceCommand cvs server
PasswordAuthentication yes
Damit eine Verbindung hergestellt werden kann, muss ggf. der Server aus den known_hosts im Verzeichnis ~/.ssh/ auf dem Client-PC entfernt werden.
Docker-Installation
Wir installieren Docker.
Installation Docker Engine
sudo apt install ca-certificates curl gnupg lsb-release
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu \
$(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt update
sudo apt-get install docker-ce docker-ce-cli containerd.io
Installation Docker Compose
sudo mkdir -p /usr/local/lib/docker/cli-plugins/
sudo curl -SL https://github.com/docker/compose/releases/download/v2.23.3/docker-compose-linux-x86_64 -o /usr/local/lib/docker/cli-plugins/docker-compose
sudo chmod +x /usr/local/lib/docker/cli-plugins/docker-compose
Auf der Release-Page in Github kann man sehen, ob es eine neuere Version gibt. In diesem Fall müsste die URL angepasst werden.
Test Docker
Wir schauen uns mal an, ob Docker funktioniert.
sudo docker run hello-world
docker compose version
Wenn keine Fehler aufgekommen sind, löschen wir den soeben erstellen Container und das Image wieder.
sudo docker container prune
sudo docker image prune -a
DNS
Der Server soll unseren DNS managen. Adguard Home wird dafür zuständig sein.
Installation Adguard Home
Als Erstes installieren wir AdGuard Home (eine Alternative zu pi-hole, NextDNS oder Blocky). Ich möchte das über Docker laufen lassen. Für meine Container erstelle ich mir ein neues Verzeichnis.
sudo mkdir -p /root/docker/containers
sudo chmod 700 /root/docker/containers
sudo mkdir /root/docker/containers/adguard
sudo chmod 700 /root/docker/containers/adguard
Und für die Daten erstelle ich ein neues Verzeichnis.
sudo mkdir -p /srv/adguard/data
sudo mkdir /srv/adguard/data/work
sudo mkdir /srv/adguard/data/conf
sudo chmod -R 750 /srv/adguard
Nun erstelle ich eine neue Docker-Compose Datei.
sudo vim /root/docker/containers/adguard/docker-compose.yml
Da kommt folgender Inhalt rein:
version: "3"
services:
adguard:
image: adguard/adguardhome:latest
container_name: adguard
restart: unless-stopped
ports:
- "53:53/tcp"
- "53:53/udp"
- "80:80/tcp"
- "3000:3000/tcp"
volumes:
- /srv/adguard/data/work:/opt/adguardhome/work
- /srv/adguard/data/conf:/opt/adguardhome/conf
environment:
TZ: Europe/Berlin
Zeit, den Container zu starten.
sudo docker compose -f /root/docker/containers/adguard/docker-compose.yml up -d
Dies sollte mit einem Fehler enden, weil Port 53 nicht mehr frei ist.
Mit sudo netstat -lnptu
kann man sehen, welche Ports verwendet werden. 53 wird vom systemd-resolv verwendet. Das ändern wir.
sudo vim /etc/systemd/resolved.conf
Hier die Zeile #DNSStubListener=yes
auskommentieren und zu DNSStubListener=no
ändern. Danach den Resolver und den Container neu starten.
sudo systemctl restart systemd-resolved
sudo docker compose -f /root/docker/containers/adguard/docker-compose.yml up -d
Sollte kein Fehler aufgetreten sein, kann man versuchen über einen Webbrowser das Dashboard zu erreichen (unter Port 3000), also zum Beispiel 192.168.123.150:3000.
Hier sollte einen bereits der Installer begrüßen.
Die zweite Seite lasse ich so wie sie ist, auf der dritten erstelle ich ein Benutzerkonto. Andere Einstellungen nehme ich vorerst nicht vor, sondern melde mich mit meinem gerade erstellten Konto an.
Unter Filter > DNS-Sperrliste lassen sich Sperrlisten hinzufügen, einige Listen werden bereits vorgegeben. Nachdem man alle Listen ausgewählt hat, die man nutzen möchte, sollte man einmal auf “Nach Updates suchen” drücken. Die Aktivierung im Router nehmen wir später vor. Es ist vermutlich auch sinnvoll eine Whitelist hinzuzufügen oder sich selbst eine zu erstellen. Im Internet finden sich einige, zum Beispiel die Whitelist von Hl2Guide oder eine kürzere Whitelist in meinem Repository.
Ich stoppe den Adguard Container wieder und bearbeite ihn.
sudo docker compose -f /root/docker/containers/adguard/docker-compose.yml down
sudo vim /root/docker/containers/adguard/docker-compose.yml
version: "3"
services:
adguard:
image: adguard/adguardhome:latest
container_name: adguard
restart: unless-stopped
ports:
- "53:53/tcp"
- "53:53/udp"
- "85:80/tcp"
volumes:
- /srv/adguard/data/work:/opt/adguardhome/work
- /srv/adguard/data/conf:/opt/adguardhome/conf
environment:
TZ: Europe/Berlin
Ich habe Port 3000 wieder entfernt, und den öffentlichen Port von 80 auf 85 geändert (80 brauche ich später für andere Dienste).
Danach ändern wir den Resolver vor dem Start in der Konfiguration.
sudo mkdir /etc/systemd/resolved.conf.d
sudo vim /etc/systemd/resolved.conf.d/adguard.conf
In die neue Datei kommt folgender Text:
[Resolve]
DNS=127.0.0.1
DNSStubListener=no
Diese Konfiguration müssen wir noch aktivieren:
mv /etc/resolv.conf /etc/resolv.conf.backup
ln -s /run/systemd/resolve/resolv.conf /etc/resolv.conf
sudo systemctl restart systemd-resolved
Wir ändern auch die Datei, die wir ganz am Anfang geändert haben.
sudo vim /etc/netplan/00-installer-config.yaml
Hier kommt als Nameserver 127.0.0.1 rein.
sudo netplan apply
Nun kann der Container gestartet werden.
sudo docker compose -f /root/docker/containers/adguard/docker-compose.yml up -d
Die Weboberfläche sollte nun auf Port 85 zu erreichen sein.
Router-Einstellung
Damit der DNS-Server auch von den Geräten in meinem Netzwerk verwendet wird, muss ich die Einstellungen im Router ändern.
Bei der Fritz!Box findet sich die Einstellung bei Netzwerk > Netzwerkeinstellungen > IPv4-Adressen. Dort trage ich die IP-Adresse des Servers ein.
Auch unter Internet > Zugangsdaten > DNS-Server trage ich als bevorzugten DNS-Server die IP-Adresse des Adguard-Servers ein.
Im Dashboard von AdGuard sollte indessen zu sehen sein, dass DNS-Abfragen vom Server bearbeitet wurden.
Proxy-Server
Als Nächstes möchte ich einen Proxyserver einrichten. Zur Wahl stehen Traefik, Caddy, Nginx und Apache und weitere.
Installation Nginx Proxy Manager
Ich möchte dieses Mal den Nginx Proxy Manager benutzen. Dafür brauche ich ein neues Verzeichnis für den Container und für die Daten.
sudo mkdir /srv/nginxproxymanager
sudo mkdir /srv/nginxproxymanager/data
sudo mkdir /srv/nginxproxymanager/certs
sudo mkdir /srv/nginxproxymanager/db
sudo chmod -R 750 /srv/nginxproxymanager
sudo mkdir /root/docker/containers/nginxproxymanager
sudo chmod 700 /root/docker/containers/nginxproxymanager
Ich erstelle ein Netzwerk für den Proxy-Server.
sudo docker network create npm
Dann erstelle ich noch einen Ordner für meine Docker Secrets.
sudo mkdir /root/docker/secrets/
sudo chown root:root /root/docker/secrets/
sudo chmod 600 /root/docker/secrets/
Da kommen mein Passwort und mein Root-Passwort für die Datenbank rein.
sudo bash -c 'echo $(openssl rand -base64 32) > /root/docker/secrets/npm-password.txt'
sudo bash -c 'echo $(openssl rand -base64 32) > /root/docker/secrets/npm-root-password.txt'
Jetzt muss die Docker Compose Datei erstellt werden.
sudo vim /root/docker/containers/nginxproxymanager/docker-compose.yml
version: "3.7"
networks:
npm:
name: npm
internal:
external: false
secrets:
npm-password:
file: /root/docker/secrets/npm-password.txt
npm-root-password:
file: /root/docker/secrets/npm-root-password.txt
services:
npm-app:
image: "jc21/nginx-proxy-manager"
container_name: npm-app
restart: unless-stopped
ports:
- "80:80"
- "443:443"
- "81:81"
environment:
DB_MYSQL_HOST: "npm-db"
DB_MYSQL_PORT: 3306
DB_MYSQL_USER: "npm"
DB_MYSQL_NAME: "npm"
DB_MYSQL_PASSWORD__FILE: /run/secrets/npm-password
DISABLE_IPV6: "true"
networks:
- npm
- internal
volumes:
- /srv/nginxproxymanager/data:/data
- /srv/nginxproxymanager/certs:/etc/letsencrypt
secrets:
- npm-password
npm-db:
image: "jc21/mariadb-aria"
container_name: npm-db
restart: unless-stopped
environment:
MYSQL_ROOT_PASSWORD__FILE: /run/secrets/npm-root-password
MYSQL_DATABASE: "npm"
MYSQL_USER: "npm"
MYSQL_PASSWORD__FILE: /run/secrets/npm-password
networks:
- internal
volumes:
- /srv/nginxproxymanager/db:/var/lib/mysql
secrets:
- npm-password
- npm-root-password
Anschließend muss dieser Stack gestartet werden.
sudo docker compose -f /root/docker/containers/nginxproxymanager/docker-compose.yml up -d
Am besten schaut man noch, ob alles funktioniert, oder bereits Fehlermeldungen auftauchen.
sudo docker logs npm-app
Sollte alles soweit ok sein, kann man sich unter der IP-Adresse des Servers und dem Port 81 mit dem Standardkonto anmelden.
Benutzername: admin@example.com
Password: changeme
Am Ende sollte man im Dashboard landen.
DDNS
Als Nächstes sollte eine Domain eingerichtet werden, die auf das Heimnetzwerk geroutet wird. Die dynamische Anpassung der DNS-Einträge kann die Fritz!Box übernehmen. Ist das nicht möglich, kann man auch ein Script auf dem Ubuntu-Server nutzen. Ich fasse hier kurz die Schritte zusammen.
Domain Update Script
Zunächst sollte eine Domain gekauft werden. Der Anbieter meiner Wahl ist netcup (Affiliate Link). Hier kostet eine .de-Domain dauerhaft 5 Euro im Jahr.
Anschließend werden ein API-Key und das API-Password von netcup benötigt. Dies erhält man im CCP
Wenn wir keinen Webspace bei Netcup haben, brauchen wir PHP um die DNS Einträge zu aktualisieren. Statt das Skript von Lars-Sören Steck direkt auszuführen, packe ich es in einen Docker Container.
sudo mkdir /root/docker/containers/ddns
sudo chmod 700 /root/docker/containers/ddns
sudo git clone https://github.com/stecklars/dynamic-dns-netcup-api /root/docker/containers/ddns/script
sudo vim /root/docker/containers/ddns/Dockerfile
Jetzt erstellen wir das Dockerfile.
FROM php:8.1-cli
COPY ./script .
ENTRYPOINT ["php", "./update.php"]
Es ist kurz, sollte aber den Zweck erfüllen.
Danach muss noch das config angepasst werden.
sudo cp /root/docker/containers/ddns/script/config.dist.php /root/docker/containers/ddns/script/config.php
sudo vim /root/docker/containers/ddns/script/config.php
Hier muss die Kundennummer geändert werden, der API-Key und das API-Password. Die Zeile mit DOMAINLIST
muss am Ende ungefähr so aussehen:
...
define('DOMAINLIST', 'meinedomain.de: *');
...
Damit werden alle Subdomains der Domain meinedomain.de
aktualisiert, die nicht eine spezifischere Zuordnung haben.
Dann brauchen wir noch die Docker Compose Datei.
sudo vim /root/docker/containers/ddns/docker-compose.yml
version: "3.7"
services:
ddns:
container_name: ddns
build:
context: /root/docker/containers/ddns
dockerfile: Dockerfile
image: ddns
Wir testen den Container mit und schauen uns die Logs an:
sudo docker compose -f /root/docker/containers/ddns/docker-compose.yml up --build
Automatisches DDNS-Update
Nun muss der Container noch automatisch gestartet werden.
sudo vim /etc/systemd/system/ddns.service
[Unit]
Description=Dynamischer DNS Service
Requires=docker.service
After=docker.service
[Service]
Type=oneshot
ExecStart=/usr/bin/docker compose -f /root/docker/containers/ddns/docker-compose.yml up --build
Auch hier testen wir, ob alles funktioniert:
sudo systemctl daemon-reload
sudo systemctl start ddns.service
sudo systemctl status ddns.service
Es dürfte im Log stehen, dass die IP-Adresse nicht aktualisiert wurde, sondern gleich geblieben ist. Jetzt lässt sich ein Timer erstellen, der denn Container in einem bestimmten Intervall neu startet.
sudo vim /etc/systemd/system/ddns.timer
[Unit]
Description=Dynamischer DNS Service
[Timer]
OnBootSec=5min
OnUnitInactiveSec=30min
[Install]
WantedBy=timers.target
OnUnitInacticeSec definiert einen Zeitgeber relativ zu dem Zeitpunkt, an dem die Einheit, die der Zeitgeber aktiviert, zuletzt deaktiviert wurde. Das heißt, der Container wird neu gestartet, wenn er in den letzten 30 Minuten nicht aktiv war.
sudo systemctl enable ddns.timer
sudo systemctl start ddns.timer
sudo systemctl status ddns.timer
sudo systemctl list-timers
Routing
Unsere Domain zweigt jetzt auf die öffentliche IP-Adresse unseres Heimnetzwerks. Die Anfrage wird aber noch nicht an den Proxy weitergegeben.
Freigabe HTTP und HTTPS
Deshalb richten wir eine Portweiterleitung ein. Im Router müssen Port 80 sowie 443 an den Server weitergeleitet werden.
Damit landen alle http und https Anfragen auf Port 80/443 beim Server. Dort werden Sie dann vom Proxy weitergeleitet.
Lokales Routing
Wenn ich mich im Heimnetzwerk befinde, möchte ich, dass die Anfrage auf eine Subdomain zum einen nicht unterbunden wird. Außerdem hätte ich lieber den direkten Weg zum Server.
Der DNS-Rebind-Schutz lässt sich im Router unterbinden.
Danach erstellte ich noch in Adguard eine benutzerdefinierte Filterregel.
Alternativ lässt sich das auch mit einer DNS-Umschreibung bewerkstelligen. Dieser Weg ist vorteilhafter, wenn man die Domain nicht anderweitig verwendet.
Die Regel sorgt dafür, dass alle Anfragen auf die Subdomains direkt an den Server weitergeleitet werden. Wenn wir Filterregeln nutzen, können wir einstellen, dass nicht nur proxy.domain.de, sondern auch adguard.domain.de auf die interne Domain des Servers weitergeleitet werden.
Das können wir entweder so eintragen:
192.168.178.100 adguard.domain.de proxy.domain.de
Oder folgendermaßen:
192.168.178.100 adguard.domain.de
192.168.178.100 proxy.domain.de
Proxy Host
Wir rufen wieder das Dashboard vom Nginx Proxy Manager auf. Zunächst wollen wir eine neue Filterregel, um nur lokalen Zugriff zu erlauben.
Die einzige Einstellung, die geändert werden sollte, ist der Filter nach IP-Adressen. Wenn die IP-Adresse des Clients sich nicht im Heimnetz befindet, soll die Anfrage abgelehnt werden. Das Subnetz muss an das eigene Netzwerk angepasst werden.
Anschließend benötigen wir einen neuen Proxy-Host.
Wir leiten die Anfragen an den Host “npm-app” weiter. Wenn der Host in der Docker-Compose Datei anders benannt wurde, muss der Eintrag angepasst werden. Unter Access List kann die gerade erstellte Filterregel ausgewählt werden. Nachdem der Proxy Host erstellt wurde, muss er nochmal bearbeitet werden. Diesmal im Bereich SSL. Hier kann ausgewählt werden, dass ein neues Zertifikat erstellt werden soll. Außerdem möchten wir SSL erzwingen.
Wir testen, ob das funktioniert, indem im Browser die eben angegebene Domain aufgerufen wird. Danach testen wir das am besten auch noch auf einem Handy, welches nicht mit dem WLAN verbunden ist. Das erhoffte Verhalten ist nun, dass sich der Nginx Proxy Manager auf dem Gerät im Netzwerk öffnet. Zudem wird die Domain jetzt auf https:// geleitet und es erscheint ein Schloss-Symbol in der Adresszeile. Sobald wir es mit einem Gerät außerhalb des Netzwerkes testen, erscheint der Fehler 403.
Wir richten auch einen Proxy Host für Adguard nach dem gleichen Prinzip ein.
Sobald wir die Funktionalität testen, merken wir, dass es nicht funktioniert. Das liegt zum einen daran, dass Adguard intern auf Port 80 und nicht 85 hört (außer man hat es bei der Einrichtung geändert). Zum Anderen befindet sich Adguard auf einem isolierten Netzwerk. Wir müssen also im Proxy Manager den Port von 85 auf 80 umstellen und in der Docker-Compose.yml das Netzwerk anpassen. Dazu stoppen wir den Container und bearbeiten die Compose Datei.
sudo docker compose -f /root/docker/containers/adguard/docker-compose.yml down
sudo vim /root/docker/containers/adguard/docker-compose.yml
version: "3"
networks:
npm:
name: npm
services:
adguard:
image: adguard/adguardhome:latest
container_name: adguard
restart: unless-stopped
ports:
- "53:53/tcp"
- "53:53/udp"
volumes:
- /srv/adguard/data/work:/opt/adguardhome/work
- /srv/adguard/data/conf:/opt/adguardhome/conf
environment:
TZ: Europe/Berlin
networks:
- npm
Die Veröffentlichung des öffentlichen Ports 85 zur Weiterleitung an den internen Port 80 ist nicht mehr notwendig. Adguard Home lässt sich dadurch nur noch über die eingerichtete Domain erreichen, nicht mehr direkt über die IP-Adresse inklusive Port.
sudo docker compose -f /root/docker/containers/adguard/docker-compose.yml up -d
Jetzt sollte der Proxy Host funktionieren. Wir können ebenso den Nginx Proxy Manager selbst ändern.
sudo docker compose -f /root/docker/containers/nginxproxymanager/docker-compose.yml down
sudo vim /root/docker/containers/nginxproxymanager/docker-compose.yml
version: "3.7"
networks:
npm:
name: npm
internal:
external: false
secrets:
npm-password:
file: /root/docker/secrets/npm-password.txt
npm-root-password:
file: /root/docker/secrets/npm-root-password.txt
services:
npm-app:
image: "jc21/nginx-proxy-manager"
container_name: npm-app
restart: unless-stopped
ports:
- "80:80"
- "443:443"
environment:
DB_MYSQL_HOST: "npm-db"
DB_MYSQL_PORT: 3306
DB_MYSQL_USER: "npm"
DB_MYSQL_NAME: "npm"
DB_MYSQL_PASSWORD__FILE: /run/secrets/npm-password
DISABLE_IPV6: "true"
networks:
- npm
- internal
volumes:
- /srv/nginxproxymanager/data:/data
- /srv/nginxproxymanager/certs:/etc/letsencrypt
secrets:
- npm-password
npm-db:
image: "jc21/mariadb-aria"
container_name: npm-db
restart: unless-stopped
environment:
MYSQL_ROOT_PASSWORD__FILE: /run/secrets/npm-root-password
MYSQL_DATABASE: "npm"
MYSQL_USER: "npm"
MYSQL_PASSWORD__FILE: /run/secrets/npm-password
networks:
- internal
volumes:
- /srv/nginxproxymanager/db:/var/lib/mysql
secrets:
- npm-password
- npm-root-password
sudo docker compose -f /root/docker/containers/nginxproxymanager/docker-compose.yml up -d
Jetzt sollte alles über die Domains erreichbar sein. Der Proxy Manager ist eingerichtet.
Firewall
Was jetzt eingerichtet werden kann, ist eine Firewall. Der einfachste Weg ist hier UFW.
sudo ufw limit ssh comment "SSH"
sudo ufw allow proto tcp from any to any port 80 comment "Nginx Proxy Manager"
sudo ufw allow proto tcp from any to any port 443 comment "Nginx Proxy Manager"
sudo ufw route allow proto tcp from any to any port 80 comment "Nginx Proxy Manager"
sudo ufw route allow proto tcp from any to any port 443 comment "Nginx Proxy Manager"
sudo ufw allow proto tcp from 192.168.123.0/24 to any port 53 comment "DNS TCP"
sudo ufw allow proto udp from 192.168.123.0/24 to any port 53 comment "DNS UDP"
sudo ufw route allow proto tcp from 192.168.123.0/24 to any port 53 comment "DNS TCP"
sudo ufw route allow proto udp from 192.168.123.0/24 to any port 53 comment "DNS UDP"
sudo ufw logging off
sudo ufw enable
Natürlich muss auch hier das Subnetz für den Port 53 angepasst werden.
Docker sind diese Regeln aber egal, sobald man einen Port darüber freigibt, wird die Firewall ausgehebelt. Deshalb gehe ich weiter nach der Anleitung aus diesem Repository vor.
sudo vim /etc/ufw/after.rules
Hier an das Ende der Datei einfügen:
# BEGIN UFW AND DOCKER
*filter
:ufw-user-forward - [0:0]
:ufw-docker-logging-deny - [0:0]
:DOCKER-USER - [0:0]
-A DOCKER-USER -j ufw-user-forward
-A DOCKER-USER -j RETURN -s 10.0.0.0/8
-A DOCKER-USER -j RETURN -s 172.16.0.0/12
-A DOCKER-USER -j RETURN -s 192.168.0.0/16
-A DOCKER-USER -p udp -m udp --sport 53 --dport 1024:65535 -j RETURN
-A DOCKER-USER -j ufw-docker-logging-deny -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 192.168.0.0/16
-A DOCKER-USER -j ufw-docker-logging-deny -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 10.0.0.0/8
-A DOCKER-USER -j ufw-docker-logging-deny -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 172.16.0.0/12
-A DOCKER-USER -j ufw-docker-logging-deny -p udp -m udp --dport 0:32767 -d 192.168.0.0/16
-A DOCKER-USER -j ufw-docker-logging-deny -p udp -m udp --dport 0:32767 -d 10.0.0.0/8
-A DOCKER-USER -j ufw-docker-logging-deny -p udp -m udp --dport 0:32767 -d 172.16.0.0/12
-A DOCKER-USER -j RETURN
-A ufw-docker-logging-deny -m limit --limit 3/min --limit-burst 10 -j LOG --log-prefix "[UFW DOCKER BLOCK] "
-A ufw-docker-logging-deny -j DROP
COMMIT
# END UFW AND DOCKER
sudo systemctl restart ufw
Falls ein Fehler kommen sollte, kann man nachsehen, was passiert ist.
sudo systemctl status ufw
Sollte es einen Fehler mit der Zeile geben, in der *filter
steht, muss das COMMIT
am ursprünglichen Ende der Datei in #COMMIT
geändert werden.
Die Firewall sollte nun funktionieren.
Docker Socket Proxy
Um die Sicherheit hoffentlich noch etwas zu erhöhen, erstelle ich einen Docker Socket Proxy. Mit diesem können sich dann die Apps, die Zugriff auf den Socket brauchen, verbinden, statt den richtigen zu nutzen. Wir benötigen ein neues Netzwerk für diesen Proxy.
sudo docker network create socket_proxy
Dann können wir die Docker Compose Datei erstellen.
sudo mkdir /root/docker/containers/socketproxy/
sudo chmod 700 /root/docker/containers/socketproxy/
sudo vim /root/docker/containers/socketproxy/docker-compose.yml
version: "3.7"
networks:
socket_proxy:
name: socket_proxy
services:
socketproxy:
image: "fluencelabs/docker-socket-proxy"
container_name: socketproxy
restart: unless-stopped
networks:
- socket_proxy
ports:
- "127.0.0.1:2375:2375"
volumes:
- /var/run/docker.sock:/var/run/docker.sock
privileged: true
environment:
- LOG_LEVEL=info
# 0: Kein Zugriff auf die API.
# 1: Zugriff auf die API.
- EVENTS=1
- PING=1
- VERSION=1
- AUTH=1
- SECRETS=0
- POST=1
- BUILD=1
- COMMIT=1
- CONFIGS=1
- CONTAINERS=1
- DISTRIBUTION=1
- EXEC=1
- IMAGES=1
- INFO=1
- NETWORKS=1
- NODES=0
- PLUGINS=0
- SERVICES=1
- SESSION=0
- SWARM=0
- SYSTEM=0
- TASKS=1
- VOLUMES=1
Wichtig ist es den Port 2375 nicht freizugeben. Möchte man verschiedene andere Geräte im Netzwerk den Socket Proxy nutzen lassen (dann muss auch - 127.0.0.1:2375:2375 in
- 2375:2375`geändert werden), sollte man darauf aufpassen, dass der Port wirklich nur von den Geräten erreicht werden kann, die man erwartet. Ggf. muss dann die Firewall nochmal restriktiver konfiguriert werden.
Mir reicht es erstmal, dass zumindest ein paar Funktionen nicht erreicht werden können.
sudo docker compose -f /root/docker/containers/socketproxy/docker-compose.yml up -d
Wir passen auch die Firewall an.
sudo ufw allow proto tcp from 127.0.0.1 to any port 2375 comment "Docker Socket"
sudo ufw route allow proto tcp from 127.0.0.1 to any port 2375 comment "Docker Socket"
sudo ufw reload
sudo ufw enable
Flame
Als Nächstes wäre ein Dashboard nett, von dem aus die internen Dienste oder andere Lesezeichen erreicht werden können. Quasi eine eigene Startseite für das “Internet”. Statt Homer nutze ich dieses Mal Flame.
Ich möchte ein neues Secret für das Password. Das Passwort natürlich ändern, es sollte kein '
enthalten.
sudo bash -c 'echo irgendEinPasswort > /root/docker/secrets/flame-password.txt'
sudo mkdir -p /srv/flame/data
sudo chmod -R 750 /srv/flame
sudo mkdir /root/docker/containers/flame/
sudo chmod 700 /root/docker/containers/flame/
sudo vim /root/docker/containers/flame/docker-compose.yml
Bei dieser Docker Compose Datei können wir gleich den soeben erstellten Socket nutzen.
version: '3.6'
networks:
npm:
name: npm
socket_proxy:
name: socket_proxy
services:
flame:
image: pawelmalak/flame
container_name: flame
restart: unless-stopped
networks:
- npm
- socket_proxy
volumes:
- /srv/flame/data:/app/data
secrets:
- password
environment:
- PASSWORD__FILE=/run/secrets/password
- DOCKER_HOST=tcp://socketproxy:2375
restart: unless-stopped
secrets:
password:
file: /root/docker/secrets/flame-password.txt
sudo docker compose -f /root/docker/containers/flame/docker-compose.yml up -d
Jetzt sollte ein Eintrag in Adguard angelegt werden. Der Proxy Host sollte auf Host: flame; Port: 5005
weiterleiten. Eventuell ist es nötig, das Netzwerk nochmal neu zu betreten, bevor die Änderungen wirksam werden.
In den den Einstellungen von Flame ist es nötig den Docker Socket für die Integration mit Docker zu ändern.
Jetzt können wir die Integration testen.
sudo vim /root/docker/containers/adguard/docker-compose.yml
Hier kommt nun der Label-Abschnitt hinzu.
version: "3"
services:
adguard:
image: adguard/adguardhome:latest
container_name: adguard
restart: unless-stopped
ports:
- "53:53/tcp"
- "53:53/udp"
volumes:
- /srv/adguard/data/work:/opt/adguardhome/work
- /srv/adguard/data/conf:/opt/adguardhome/conf
environment:
TZ: Europe/Berlin
labels:
- flame.type=app
- flame.name=Adguard Home
- flame.url=https://adguard.meinedomain.de
- flame.icon=advertisements
sudo vim /root/docker/containers/nginxproxymanager/docker-compose.yml
version: "3.7"
networks:
npm:
name: npm
internal:
external: false
secrets:
npm-password:
file: /root/docker/secrets/npm-password.txt
npm-root-password:
file: /root/docker/secrets/npm-root-password.txt
services:
npm-app:
image: "jc21/nginx-proxy-manager"
container_name: npm-app
restart: unless-stopped
ports:
- "80:80"
- "443:443"
environment:
DB_MYSQL_HOST: "npm-db"
DB_MYSQL_PORT: 3306
DB_MYSQL_USER: "npm"
DB_MYSQL_NAME: "npm"
DB_MYSQL_PASSWORD__FILE: /run/secrets/npm-password
DISABLE_IPV6: "true"
networks:
- npm
- internal
volumes:
- /srv/nginxproxymanager/data:/data
- /srv/nginxproxymanager/certs:/etc/letsencrypt
secrets:
- npm-password
labels:
- flame.type=app
- flame.name=Nginx Proxy Manager
- flame.url=https://proxy.meinedomain.de
- flame.icon=arrow-decision-outline
npm-db:
image: "jc21/mariadb-aria"
container_name: npm-db
restart: unless-stopped
environment:
MYSQL_ROOT_PASSWORD__FILE: /run/secrets/npm-root-password
MYSQL_DATABASE: "npm"
MYSQL_USER: "npm"
MYSQL_PASSWORD__FILE: /run/secrets/npm-password
networks:
- internal
volumes:
- /srv/nginxproxymanager/db:/var/lib/mysql
secrets:
- npm-password
- npm-root-password
sudo docker compose -f /root/docker/containers/adguard/docker-compose.yml down
sudo docker compose -f /root/docker/containers/adguard/docker-compose.yml up -d
sudo docker compose -f /root/docker/containers/nginxproxymanager/docker-compose.yml down
sudo docker compose -f /root/docker/containers/nginxproxymanager/docker-compose.yml up -d
Wenn Flame aktualisiert wurde, sollten Adguard sowie der Nginx Proxy Manager dort unter Applications auftauchen.
Git Server
Als Nächstes benötige ich einen Git Server. In der Vergangenheit habe ich gerne Gitea dafür installiert und für einen Raspberry Pi würde ich das immer noch empfehlen. Diesmal möchte ich aber ein integriertes CI/CD testen. Deshalb installiere ich jetzt OneDev.
Der Prozess sollte inzwischen weitgehend bekannt sein. Ich möchte eine PostgreSQL Datenbank für Onedev verwenden. Dafür brauche ich ein Password. Leider ist dieses Mal das Problem, dass Docker Secrets nicht unterstützt werden.
sudo bash -c 'echo "DB_PWD=$(openssl rand -base64 32)" > /root/docker/containers/onedev/.env'
sudo mkdir /root/docker/containers/onedev
sudo chmod 700 /root/docker/containers/onedev
sudo mkdir -p /root/docker/containers/onedev/data
sudo mkdir /root/docker/containers/onedev/db
sudo chmod -R 750 /root/docker/containers/onedev
sudo vim /root/docker/containers/onedev/docker-compose.yml
version: "3.7"
networks:
npm:
name: npm
socket_proxy:
name: socket_proxy
internal:
external: false
services:
onedev-app:
container_name: onedev-app
image: 1dev/server
restart: unless-stopped
networks:
- npm
- internal
- socket_proxy
volumes:
- /srv/onedev/data:/opt/onedev
environment:
DOCKER_HOST: tcp://socket_proxy:2375
hibernate_dialect: io.onedev.server.persistence.PostgreSQLDialect
hibernate_connection_driver_class: org.postgresql.Driver
hibernate_connection_url: jdbc:postgresql://onedev-db/onedev
hibernate_connection_username: onedev
hibernate_connection_password: $DB_PWD
labels:
- flame.type=app
- flame.name=OneDev
- flame.url=https://onedev.meinedomain.de
- flame.icon=git
depends_on:
- onedev-db
onedev-db:
container_name: onedev-db
image: postgres:14
restart: unless-stopped
networks:
- internal
volumes:
- /srv/onedev/db:/var/lib/postgresql/data
environment:
POSTGRES_PASSWORD: $DB_PWD
POSTGRES_USER: onedev
POSTGRES_DB: onedev
sudo docker compose -f /root/docker/containers/onedev/docker-compose.yml up -d
Anschließend einen Eintrag in Adguard für OneDev hinzufügen. Und dann einen Proxy Host im Nginx Proxy Manager (mit Websocket Support) anlegen. Der Proxy Host hat in meinem Beispiel den Host “onedev-app” und die Portnummer 6610. Benötigt man noch SSH Zugriff, muss ein weiterer Proxy Host auf Portnummer 6611 geroutet werden.
Sobald die Onedev-Seite aufgerufen wird, muss ein Administratorkonto erstellt werden.
Möchte man den anonymen Zugriff auf das eigene OneDev verhindern, lässt sich das in den Sicherheitseinstellungen unterbinden.
Möchte man anschließend, dass der Git Server auch von außen erreicht werden kann, muss man die Beschränkung auf das lokale Netzwerk aus dem Proxy Host im Nginx Proxy Manager herausnehmen.
Zeittracker
Ich hätte gerne meine eigene Zeiterfassung auf dem Server. Dafür sollte Kimai geeignet sein. Im Grunde geht aber hier um die Speicherung von Daten in Docker. Unsere bisherigen Docker-Compose Dateien haben Bind Mounts genutzt. Das “Problem” bei einem Bind-Mount ist aber, dass der Inhalt des Containers nicht automatisch auf den Rechner kopiert wird, im Gegensatz zu einem benannten Docker Volume.
Einige Images sind so konzipiert, dass sie mit einem Bind-Mount nicht funktionieren werden.
Zunächst brauchen wir aber Passwörter.
sudo mkdir /root/docker/containers/kimai
sudo chmod 700 /root/docker/containers/kimai
sudo bash -c 'echo "DB_PWD=$(openssl rand -base64 32)" > /root/docker/containers/kimai/.env'
sudo mkdir -p /srv/kimai/db
sudo chmod -R 750 /srv/kimai
sudo vim /root/docker/containers/kimai/docker-compose.yml
version: "3.7"
networks:
npm:
name: npm
internal:
external: false
services:
kimai-app:
container_name: kimai-app
image: kimai/kimai2:apache
restart: unless-stopped
networks:
- internal
- npm
volumes:
- data:/opt/kimai/var
environment:
ADMINMAIL: mailadresse@domain.de
ADMINPASS: dasPasswortZurAnmeldung
DATABASE_URL: mysql://kimai:einSicheresPassword@kimai-db:3306/kimai
TRUSTED_HOSTS: nginx,localhost,127.0.0.1,kimai.meinedomain.de
depends_on:
- kimai-db
labels:
- flame.type=app
- flame.name=Kimai
- flame.url=https://kimai.meinedomain.de
- flame.icon=timer
kimai-db:
container_name: kimai-db
image: mariadb:10.6
restart: unless-stopped
networks:
- internal
volumes:
- /srv/kimai/db:/var/lib/mysql
environment:
MARIADB_ROOT_PASSWORD: $DB_PWD
MARIADB_DATABASE: kimai
MARIADB_USER: kimai
MARIADB_PASSWORD: einSicheresPasswort
volumes:
data:
sudo docker compose -f /root/docker/containers/kimai/docker-compose.yml up -d
Nachdem dafür auch ein Eintrag in AdGuard bzw. ein Proxy Host Hostname: kimai-app; Port: 8001
auch dafür angelegt wurde, kann man sich schon bei Kimai anmelden.
Container updaten
Das Update eines Containers ist mit drei Zeilen Code erledigt. Als Beispiel möchte ich meinen Nginx-Proxy-Manager updaten. Dafür muss ich nur das neuste Image pullen und den Container neu starten.
sudo docker compose -f /root/docker/containers/nginx/docker-compose.yml pull
sudo docker compose -f /root/docker/containers/nginx/docker-compose.yml down
sudo docker compose -f /root/docker/containers/nginx/docker-compose.yml up -d
Wenn ich auch noch das alte Image loswerden möchte, kann ich das mit prune
tun.
sudo docker image prune
Mit sudo docker system df
kann man sich anschauen, wie viel Speicherplatz Docker verbraucht.
Wenn Fragen offen geblieben sind oder bei sonstigen Anmerkungen / Verbesserungsvorschlägen, kann gerne der Kommentarbereich genutzt werden.