Komplettanleitung Mariadb & Nextcloud mit Nginx Proxy
Um den Einstieg in die Installation von Nextcloud zu vereinfachen, habe ich mir überlegt eine Komplettanleitung zu schrieben, die folgende Komponenten auf einem Ubuntu 20.04 installiert:
- nginx (Webserver / Proxy)
- Let's Encrypt
- Docker
- Mariadb (in Docker)
- Nextcloud (in Docker)
- NFTables + Firewallregeln
Wichtig zu wissen ist, dass bei dieser Anleitung nicht der FPM-, sondern Apache-Image verwendet wird. Das FPM-Image wurde in einer anderen Anleitung (siehe https://dr3st.de/nextcloud-mit-nginx-und-php-fpm/) erklärt.
Außerdem enthält diese Anleitung auch die Datenbank und Nginx Installation, ist somit umfangreicher.
Vorbereitung
Es sollte vorab schon ein DNS Eintrag für die Cloud vorbereitet werden. In diesem Bespiel wird nextcloud.dr3st.de verwendet, dieser ist anzupassen. Bitte einen A oder CNAME Eintrag anlegen, der letztlich auf die öffentliche IP des Servers zeigt.
Keine iFrame- / Weiterleitungs-Einstellung...
Wichtig:
Die Mariadb (Mysql-Server) wird so so einrichtet, dass diese auch für andere Dienste verwendet werden kann, damit nicht weitere Instanzen gestartet werden müssen. Viele anleitungen sind so gebaut, dass für jede Anwendung ein eigener MySQL-Server eingerichtet werden soll, was ich unsinnig finde.
Außerdem wird die Firewall-Einstellung von Docker nicht deaktiviert, die generelle Absicherung aber dennoch mit NFTables realisiert. Somit ist gewährleistet, dass bestehende Setups möglichst nicht tangiert werden.
Wer dennoch die Firewall auferäumter haben möchte, kann den optinalien Hinweis in der Anleitung berücksichtigen. Hierbei aber bitte vorsichtig sein, da man bestehende Installationen unereichbar machen könnte!
Vorab
Die Befehle sind mit root-Rechten ausgeführt! Enige davon, gerade die Konfigurationsdateien anlegen, können nicht mit sudo ausgeführt werden.
Daher bietet sich vorab der Login als root, oder "sudo -i" an.
Pakete installieren
Zuerst intsallieren wir die nötigen Pakete für diese Anleitung. Dies sind sowohl die Docker Komponenten, als auch die Firewall und ein Mysql-Client zum verwalten der Datenbank.
apt update && \
apt install nftables docker.io docker-compose mariadb-client pwgen
Firewall konfigurieren
Zuerst richten wir die Firewall ein um sicherzugehen, das der Datenbank-Server nach der Installation nicht direkt im Internet erreichbar ist.
# File: /etc/nftables.conf
#!/usr/sbin/nft -f
flush ruleset
table inet filter {
chain input {
type filter hook input priority 0;
# lokale Verbindungen erlauben
iifname lo accept
# MySQL Port für Docker-Netz und Apps Netz erlauben
ip saddr { 172.17.0.0/16, 10.11.12.0/24 } tcp dport 3306 accept
# alle anderen Zugriffe werden blockiert
tcp dport 3306 drop
}
chain forward {
type filter hook forward priority 0;
}
chain output {
type filter hook output priority 0;
}
}
Danach wird die Konfiguration geprüft und angewendet:
# -c (Check) -f (File)
nft -c -f /etc/nftables.conf
#Keine Fehlermeldung? Gut ... :
systemctl restart nftables.service
systemctl enable nftables.service
Mariadb Container anlegen
zunächst legen wir die Konfiguration an, hierbei wird ein Passwort gewürfelt und die docker-compose.yml generiert.
# Passwort generieren (32 Stellen, siehe head -c 32)
MDB_PW=$(openssl rand 512 | sha512sum | head -c 32)
# Ordner anlegen und betreten
mkdir -p /srv/docker/git/mariadb
# Konfiguration anlegen (einfach kopieren)
cat > /srv/docker/git/mariadb/docker-compose.yml <<EOF
version: "3"
services:
mariadb:
image: mariadb:10.6
container_name: mariadb
network_mode: host
environment:
TZ: "UTC"
MYSQL_ROOT_PASSWORD: "${MDB_PW}"
volumes:
- /srv/docker/containers/mariadb/data:/var/lib/mysql
restart: unless-stopped
EOF
Zusätzlich speichern wir die Verbindungsdaten in der lokalen "my.cnf" damit eine Verbindung ohne Passwort-Eingabe möglich ist:
cat > ${HOME}/.my.cnf <<EOF
[mysql]
host=127.0.0.1
user=root
password=${MDB_PW}
[client]
host=127.0.0.1
user=root
password=${MDB_PW}
[mysqldump]
host=127.0.0.1
user=root
password=${MDB_PW}
EOF
Danach starten man den Container:
cd /srv/docker/git/mariadb
docker-compose pull
docker-compose up -d
Das Testen der Verbindung ist nun möglich und es werden die Logindaten für die Nextcloud-Datenbank angelegt:
# Passwort generieren und den Admin Username eintippen
NCDB_PW=$(openssl rand 512 | sha512sum | head -c 32)
ADMIN_PW=$(openssl rand 512 | sha512sum | head -c 32)
# Eingaben notwendig! Hier als Beispiel:
read -r -p "enter Nextcloud Admin User-Email:" ADMIN_USER
admin@dr3st.de
read -r -p "enter Nextcloud Domain:" NC_DOMAIN
nextcloud.dr3st.de
# diese Befehle legen die Datenbank und Zugangsdaten an
mysql -e 'CREATE DATABASE nextcloud /*!40100 DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci */;'
mysql -e "GRANT ALL PRIVILEGES ON nextcloud.* TO 'nextcloud'@'%' IDENTIFIED BY '${NCDB_PW}';"
Nextcloud Container anlegen
mkdir -p /srv/docker/git/nextcloud
# Netzwerk anlegen, damit dieses von mehreren Anwendungen
# genutzt werden kann und nicht an einer Docker-Compose Datei hängt
docker network create --gateway 10.11.12.1 --subnet 10.11.12.0/24 --opt com.docker.network.bridge.name=br_apps br_apps
# Nextcloud docker-compose.yml anlegen (Passwort wird durch Variable eingetragen!)
cat > /srv/docker/git/nextcloud/docker-compose.yml <<EOF
version: '3'
services:
nextcloud:
container_name: nextcloud
image: nextcloud:24
volumes:
- /srv/docker/containers/nextcloud/data/:/var/www/html/
# Passwörter ausgelagert
- ./secrets/:/run/secrets/:ro
environment:
MYSQL_DATABASE_FILE: "/run/secrets/mysql_database.txt"
MYSQL_USER_FILE: "/run/secrets/mysql_user.txt"
MYSQL_PASSWORD_FILE: "/run/secrets/mysql_password.txt"
MYSQL_HOST: "10.11.12.1"
NEXTCLOUD_ADMIN_USER_FILE: "/run/secrets/admin_user.txt"
NEXTCLOUD_ADMIN_PASSWORD_FILE: "/run/secrets/admin_password.txt"
NEXTCLOUD_TRUSTED_DOMAINS: "${NC_DOMAIN}"
TRUSTED_PROXIES: "10.11.12.1"
restart: unless-stopped
networks:
default:
ipv4_address: 10.11.12.2
networks:
default:
external:
name: br_apps
EOF
# Container starten
cd /srv/docker/git/nextcloud
mkdir -p secrets
echo "${NCDB_PW}" > secrets/mysql_password.txt
echo "${ADMIN_PW}" > secrets/admin_password.txt
echo "${ADMIN_USER}" > secrets/admin_user.txt
echo "nextcloud" > secrets/mysql_database.txt
echo "nextcloud" > secrets/mysql_user.txt
docker-compose pull
docker-compose up -d
# Admin Passwort zum notieren ausgeben
cat secrets/admin_password.txt
Nginx
Zuerst wird nginx installiert und der Autostart aktiviert:
apt install nginx certbot
systemctl enable nginx
Danach wird das Zertifikat von Let's Encrypt angefordert. Dieses wird übrigens automatisch erneuert, weil das Certbot-Paket einen Cronjob mitliefert (/etc/cron.d/certbot).
# email@domain.de ersetzen! Dies muss nicht die Cloud Admin-Email sein!
certbot certonly --webroot -w /var/www/html/ --email email@domain.de --non-interactive --agree-tos -d ${NC_DOMAIN} --rsa-key-size 4096
# im Normalfall wird eine ähnliche Ausgabe angezeigt:
#IMPORTANT NOTES:
# - Congratulations! Your certificate and chain have been saved at:
# /etc/letsencrypt/live/..../fullchain.pem
Und die Nginx-Konfiguration angelegt.
Wichtig hierbei ist die Domain anzupassen!
# NC_DOMAIN wurde weiter oben bereits erfragt und nun
# hier verwendet und automatisch eingetragen!
cat > /etc/nginx/sites-enabled/${NC_DOMAIN}.conf <<EOF
server {
server_name ${NC_DOMAIN};
listen 0.0.0.0:80;
location ~ .well-known {
root /var/www/html;
}
location / {
return 301 https://${NC_DOMAIN}$request_uri;
}
}
server {
server_name ${NC_DOMAIN};
listen 0.0.0.0:443 ssl http2;
access_log /var/log/nginx/${NC_DOMAIN}.access.log;
error_log /var/log/nginx/${NC_DOMAIN}.error.log;
root /var/www/html;
ssl_certificate /etc/letsencrypt/live/${NC_DOMAIN}/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/${NC_DOMAIN}/privkey.pem;
client_max_body_size 4096m;
proxy_max_temp_file_size 4096m;
# Proxy for nextcloud docker
location / {
add_header Strict-Transport-Security "max-age=63072000" always;
add_header X-Frame-Options SAMEORIGIN;
proxy_cookie_path / "/; secure; HttpOnly";
# And don't forget to include our proxy parameters
include /etc/nginx/proxy_params;
# Connect to local port
proxy_pass http://10.11.12.2:80;
}
}
EOF
Und nginx reloaden:
nginx -t && systemctl reload nginx.service
Im Anschluss sollte die Nextcloud im Browser erreichbar sein. Die Logindaten entsprechen denen, die man selbst festgelegt hat.
Ausgeben kann man diese aber noch mal nachträglich:
cd /srv/docker/git/nextcloud
# Benutzername
cat secrets/admin_user.txt
# Passwort
cat secrets/admin_password.txt
Nachwort und Netzwerk-Information

Warum das separate Netz?
Man möchte dem Nextcloud-Container und allen eventuell noch folgenden Anwendungen statische IP-Adressen geben um diese in der Nginx-Konfiguration hinterlegen zu können. In diesem Beispiel ist es die 10.11.12.2, hätte man allerdings das Docker Netzwerk genommen (docker0), hätte dies irgendeine IP aus dem Netz 172.17.0.0/16 sein können., weil dieses Netz keine statische Zuweisung erlaubt.
Ich bevorzuge allerdings planbare Konfigurationen und wähle daher diesen Weg, denn somit spart man sich den unnötigen Docker-Proxy, der in vielen Anleitungen genutzt wird. Nextcloud kann nämlich von Nginx direkt erreicht werden!
MariaDB und Nginx laufen im Standard Netzwerk (Namespace), sodass diese sowohl an öffentlichen IP-Adressen, als auch allen Docker Netzwerken lauschen können. Deshalb sind weitere Anwendungen auch in der Lage mit Mariadb zu kommunizieren (hier z.B. 10.11.12.1:3306). Anwendungen in anderen Netzen (z.B. docker0, oder anderen zukünftigen br-apps2, br-xyz, ...) sind somit in der Lage über die IP-Adresse des Hosts Verbindungen aufzubauen.
Vereinfacht in folgender Tabelle:
Netzwerk | IP-Netz | Host-IP | Container IPs |
---|---|---|---|
br-apps | 10.11.12.0/24 | 10.11.12.1 | 10.11.12.2 - 10.11.13.254 |
br-apps2 | 192.168.22.0/24 | 192.168.22.1 | 192.168.22.2 - 192.168.22.254 |
neuland | 172.19.199.0/24 | 172.19.199.1 | 172.19.199.2 - 172.19.199.254 |
Wie man erkennen kann, erhält der Host selbst die .1 in den Netzen, sodass dieser jede Anwendung erreichen kann, die Anwendungen allerdings auch den Host und somit die Datenbank und den Nginx (und alle Anwendungen im Host-Namespace). Das bedeutet die "network_mode: host" oder "--network host" Einstellung übrigens.
Weil jedes Netz für sich eine Layer2 Isolation bereitstellt, können die Netze untereinander nur sprechen, wenn der Host die Anfragen zwischen den Netzen routet. Aber das führt an dieser Stelle zu weit ;)