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 (oder Debian basiertem OS) installiert:

  • nginx (Webserver / Proxy)
  • Let’s Encrypt
  • Docker
  • Mariadb (in Docker)
  • Nextcloud (in Docker)
  • NFTables + Firewallregeln

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

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 beeinträchtigt werden. Wer dennoch die Firewall aufgeräumter haben möchte, kann den optinalen Hinweis in der Anleitung berücksichtigen. Hierbei aber bitte vorsichtig sein, da man bestehende Installationen unereichbar machen könnte!

Hinweis

Die Befehle sind mit root-Rechten ausgeführt! Enige davon, gerade die Konfigurationsdateien anlegen, können nicht mit sudo ausgeführt werden, weil die Berechtigungen sonst fehlerhaft sind. Daher bietet sich vorab der Login als root, oder “sudo -i” an.

Installation

apt update && \
apt install nftables docker.io docker-compose mariadb-client pwgen

Firewall

Konfiguration

Zuerst richten wir die Firewall ein um sicherzugehen, das der Datenbank-Server nach der Installation nicht direkt im Internet erreichbar ist.
Existiert die Konfiguration bereits, sollte Zeile 12 berücksichtigt werden.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
#!/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
  }

  chain forward {
    type filter hook forward priority 0;
  }
  
  chain output {
    type filter hook output priority 0;
  }
}
Firewall Restart
# -c (Check) -f (File)
nft -c -f /etc/nftables.conf

#Keine Fehlermeldung? Gut ... :
systemctl restart nftables.service
systemctl enable  nftables.service

MariaDB

zunächst legen wir die Konfiguration an, hierbei wird ein Passwort gewürfelt und die docker-compose.yml generiert.

Container Konfiguration
# 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
Client Config (my.cnf)

Zusätzlich speichern wir die Verbindungsdaten in der lokalen “my.cnf” damit eine Verbindung ohne Passwort-Eingabe möglich ist:

[ ! -f "${HOME}/.my.cnf" ] && \
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
Container start
cd /srv/docker/git/mariadb
docker-compose pull
docker-compose up -d
Datenbank Test & Zugangsdaten

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

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 & Certbot (Let’s Encrypt)

Zuerst wird nginx installiert und der Autostart aktiviert.

apt install nginx certbot
systemctl enable nginx
Zertifikat erzeugen

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
Nginx reload
nginx -t && systemctl reload nginx.service

Zugangsdaten

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

networking networking

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.

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 jeden Container erreichen kann. Die Container erreichen den Host (und Anwendungen im selben Netzwerk).
Somit können Container die Datenbank, den Webserver und alle Anwendungen im Host-Namespace erreichen, sofern diese mittels Firewall nicht unterbunden werden.

Die Nutzung des Host Namespaces wird durch “network_mode: host” oder “–network host” Einstellung festgelegt. Weil jedes Netzwerk für sich eine Layer2 Domäne darstellt, können die Netze untereinander nur sprechen, wenn der Host die Anfragen zwischen den Netzen routet.