Container

Subsections of Container

Subsections of Bitwarden

self-hosted Bitwarden Server

Was ist Bitwarden

Für alle die Bitwarden noch nicht kennen:

Es handelt sich hierbei um einen quelloffenen Passwort-Manager, der mittels Ende-zu-Ende-Verschlüsselung Passwörter speichert, synchronisiert und per Webinterface, aber auch vielen Apps und Browsererweiterungen bereitstellt. Ohne Master-Kennwort verliert man den Zugang zum Passwortspeicher und Serverbetreiber können Kennwörter auch nicht auslesen. Zusätzlich kann ein Eintrag für mehrere Apps und Webseiten gelten, indem man durch Regeln festlegt, wofür der Eintrag verwendet werden darf. Ein Beispiel:

Ihr nutzt die Nextcloud-App, aber auch das Webinterface. So kann in Bitwarden ein gemeinsamer Login für die Webseite https://meine.nextcloud.com, aber auch die App com.nextcloud.client hinterlegt werden. Bitwaren bietet beim automatischen Ausfüllen die Speicherung der URL an. Viele Firmen nutzen auch multiple Dienste mit LDAP mit einem gemeinsamen Login, diese können ebenfalls abgebildet werden.

Für Logindaten mit Zertifikaten bietet Bitwarden die Speicherung von Dateianhängen an und jedem Datensatz können weitere Klartext, aber auch verdeckte Zusatzfelder, die man selbst benennen kann, hinzugefügt werden.

Grundsätzlich kann man Bitwarden als den Passwort-Manager schlecht hin bezeichnen, da man seine Daten selbst hosted und mit den vielen Zugriffsmöglichkeiten endlich eine Lösung für alle gängigen Geräte hat.

Und braucht man für die 2-Faktor-Authentifizierung einen OTP-Generator, dann kann Bitwarden auch das übernehmen …
Aber genug der einleitenden Worte, auf zur Installation, basierend auf Docker.

Vorbereitung

Eine grundlegende Anleitung findet man direkt beim Hersteller, wobei in dieser Anleitung leichte Anpassungen vorgenommen werden: https://bitwarden.com/help/install-on-premise-linux/.

Zunächst legen wir die Struktur an, der Ordner kann frei gewählt werden:

# Ordner anlegen und betreten
mkdir -p /srv/docker/bitwarden
cd /srv/docker/bitwarden

# lädt das Verwaltungsskript von Bitwarden runter
curl -Lso bitwarden.sh https://go.btwrdn.co/bw-sh && chmod 700 bitwarden.sh

Dann wird Bitwarden installiert (Images gepullt, Verzeichnisse angelegt):

cd  /srv/docker/bitwarden
./bitwarden.sh install

Wärend der Installation werden verschiedene Fragen gestellt:

# Hier wird die Domain für den Aufruf festgelegt
Enter the domain name for your Bitwarden instance (ex. bitwarden.example.com): bitwarden.mydomain.com

# Let's Encrypt lassen wir vom vorgelagerten Nginx regeln, daher nein
Do you want to use Let's Encrypt to generate a free SSL certificate? (y/n): n

# Datenbankname, kann Standard bleiben, einfach Enter
Enter the database name for your Bitwarden instance (ex. vault): <ENTER>

# die Installations-ID & der Key die vorher beschafft wurde (siehe oben)
Enter your installation id (get at https://bitwarden.com/host): xxxxxxxxxxx
Enter your installation key: xxxxxxxxxxxx

# wir nutzen ein selbstsigniertes Zertifikat, was Bitwarden anlegen wird, daher hier nein auswählen
Do you have a SSL certificate to use? (y/n): n
# ja bitte!
Do you want to generate a self-signed SSL certificate? (y/n): y

Generating key for IdentityServer.
Generating a RSA private key
...................++++
writing new private key to 'identity.key'
-----

!!!!!!!!!! WARNING !!!!!!!!!!
You are not using a SSL certificate. Bitwarden requires HTTPS to operate.
You must front your installation with a HTTPS proxy or the web vault (and
other Bitwarden apps) will not work properly.


# Da wir einen eigenen nginx mit SSL Zertifikat für den öffentlichen Zugriff verwenden, benötigt Bitwarden kein LE, oder gar ein öffentliches Zertifikat. Es reicht daher aus, bei der Bitwarden # Installation ein eigenes Zertifikat generieren zu lassen. Die Kommunikation zwischen Bitwarden und dem eigenständigen nginx wird somit dennoch verschlüsselt durchgeführt.
# Ohne Zertifikats wäre es eben HTTP anstatt HTTPS und somit innerhalb des Servers unverschlüsselt...

Konfiguration

Hinweis

Die Mailkonfiguration ist nötig um Zugang zum Admininterface zu erhalten.
Für jeden Zugriff wird ein einmalig gültiger Link per E-Mail verschickt!

Der Bitwarden-Installer hat nun eine Verzeichnisstruktur angelegt und wir können weitere Konfigurationen vornehmen:

#File: /srv/docker/bitwarden/bwdata/config.yml

# Wir sorgen dafür, dass Bitwarden sich nicht auf Port 80 und 443 bindet, da hier bereits ein Webserver lauscht. Deswegen binden wir den Docker-Proxy der von Bitwarden vorgesehen ist, auf eine eigene IP-Adresse mit Port 8080 und 8443.

http_port: 172.16.1.1:8080
https_port: 172.16.1.1:8443

Datei: /srv/docker/bitwarden/bwdata/env/global.override.env

globalSettings__mail__replyToEmail=no-reply@bitwarden.mydomain.com
globalSettings__mail__smtp__host=REPLACE
globalSettings__mail__smtp__port=587
globalSettings__mail__smtp__ssl=false
globalSettings__mail__smtp__username=REPLACE
globalSettings__mail__smtp__password=REPLACE

# Hier müssen die SMTP-Auth Daten und die gewünschte E-Mailadresse hinterlegt werden, zum Beispiel:

globalSettings__mail__replyToEmail=no-reply@bitwarden.mydomain.com
globalSettings__mail__smtp__host=smtp.google.com
globalSettings__mail__smtp__port=465
globalSettings__mail__smtp__ssl=true
globalSettings__mail__smtp__username=myaccount@gmail.com
globalSettings__mail__smtp__password=super_tolles_pasword

Im späteren Verlauf kann man, nachdem ein Account angelegt wurde, die Registration für Außenstehende deaktivieren.

globalSettings__disableUserRegistration=true

Dies ist aber erst sinnvoll, wenn alle Accounts eingerichtet wurden die man benötigt ;)

Nach Anpassungen in den oben genannten Dateien müssen die Docker-Files neu genriert werden:

./bitwarden.sh rebuild

Weil wir eine feste IP-Adresse für den Bitwarden-Nginx und seine Dienste möchten, bietet es sich an, Docker mitzuteilen, dass ein bestimmtes Netz gewünscht wird. Dies lässt sich über eine sogeannte override-Datei regeln. Diese ist im Standardfall noch nicht existent und muss angelegt werden!

# File /srv/docker/bitwarden/bwdata/docker/docker-compose.override.yml
---
version: '3'

services:
  nginx:
    networks:
      default:
        ipv4_address: 172.16.1.250

networks:
  default:
    driver: bridge
    ipam:
      driver: default
      config:
        - subnet: 172.16.1.0/24
    driver_opts:
      com.docker.network.bridge.name: br_bitwarden

Hiermit wird sichergestellt, dass der nginx-Container die IP-Adresse 172.16.1.250 erhält und man über diese den Dienst erreicht. Andernfalls könnte Docker die IP-Adressen der Container nach und nach neu würfeln und man bekommt keine konsistente Konfiguration für den vorgelagerten Nginx.

Start und Test

Nun kann man Bitwarden starten und die interne Erreichbarkeit testen:

./bitwarden.sh start

# -k weil selbstsigniertes Zertifikat
curl -k https://172.16.1.250:8443
<!doctype html>......

Erhält man eine Antwort, kann nun der vorgelagerte Nginx konfiguriert werden. Je nach lokalen gegebenheiten kann dies sehr unterschiedlich passieren, gehen wir mal vom Standardfall (Ubuntu, Archlinux) aus…

Nginx und Bitwarden verbinden

# File: /etc/nginx/sites-enabled/bitwarden.mydomain.com.conf

# Dieser vHost nimmt auf Port 80 alle Anfragen an und leitet diese auf HTTPS um, außer Let's Encrypt ACME Challenges.
server {
    server_name     bitwarden.mydomain.com;

    listen          *:80;

    # Dies ist sehr individuell, mehr dazu im Bereich Let's Encrypt
    location ~ .well-known {
        root        /srv/www/letsencrypt/;
    }

    location / {
        return  301 https://$host$request_uri;
    }
}

Danach den Ordner für die LE Validierung anlegen und den Nginx neustarten / reloaden:

mkdir -p /srv/www/letsencrypt/
nginx -t && nginx -s reload

Let’s Encrypt Zertifikat anfragen

Hier wird nun der DNS-Eintrag benötigt, dieser wird nämlich öffentlich abgerufen. Wenn er noch nicht existiert, ist spätestens nun der richtige Zeitpunkt. Der Certbot legt eine ACME-Challenge-Datei unterhalb von /srv/www/letsencrypt an.

certbot certonly --webroot -w /srv/www/letsencrypt/ --email admin@mydomain.com --non-interactive --agree-tos -d bitwarden.mydomain.com

Nun ergänzen wir die Konfiguration mit dem HTTPS Host, weil das SSL-Zertifikat vorliegt und ein Reload keinen Fehler mehr produziert:

# File: /etc/nginx/sites-enabled/bitwarden.mydomain.com.conf

# Dieser bereich sollte erst eingerichtet werden, wenn ein SSL Zertifikat vorliegt! 
server {
    server_name         bitwarden.mydomain.com;

    listen              *:443 ssl;

    access_log          /var/log/nginx/bitwarden.mydomain.com.access.log;
    error_log           /var/log/nginx/bitwarden.mydomain.com.error.log;
    rewrite_log         on;

    # Platzhalter, alle Anfragen werden sowieos an Bitwarden geleitet
    root                /srv/www/default;
    index               index.php index.html index.htm;

    ssl_certificate     /etc/letsencrypt/live/bitwarden.mydomain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/bitwarden.mydomain.com/privkey.pem;

    client_max_body_size 1024M;

    location = /robots.txt {
        allow all;
        log_not_found off;
        access_log off;
    }

    location / {
        # die IP-Adresse ist nun statisch, da wir diese im docker override hinterlegt haben...
        proxy_pass  https://172.16.1.250:8443$request_uri;

        proxy_redirect off;
        proxy_set_header Host $http_host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Forwarded-Protocol $scheme;
        proxy_set_header X-Url-Scheme $scheme;
    }
}

Danach noch einmal den nginx reloaden:

nginx -t && nginx -s reload

Im Anschluss sollte Bitwarden öffentlich erreichbar sein (Adresse ist ein Beispiel!):

https://bitwarden.mydomain.com

Sofern die Mailkonfiguration korrekt ist, dürfte eine Registrierung auch klappen. Diese kann im Anschluss wie bereits erwähnt deaktiviert werden (siehe oben). Ein rebuild ist dann nötig, wodurch die Container neugestartet werden.

Viel Erfolg und Spaß mit Bitwarden ;)

Updates

Möchte man Bitwarden updaten:

cd /srv/docker/bitwarden
./bitwarden.sh update

Subsections of Docker

self hosted Docker Image Registry mit UI

Warum eine eigene Registry?

In dieser Anleitung wird eine Docker Registry eingerichtet und mittels Frontend zugängig gemacht.
Gründe hierfür können vielschichtig sein, zum Beispiel:

  • eigene Images sind nicht öffentlich erreichbar (z.B. für selbstentwickelte Software)
  • Cache um vom öffentlichen Netzwerk unabhängig zu sein
  • keine Veränderung der Images, weil man selbst diue Kontrolle über die Registry hat

Idee

Es werden 2 Container gestartet, die Registry selbst und das Frontend. Das Frontend selbst benötigt keine Persistenz und die nötigen Informationen werden vom Browser beschafft.
Die UI verhält sich somit wie eine Clientanwendung, die selbst nicht mit der Registry spricht, sondern lediglich das visualisiert, was der Browser anfragt.

Die Registry und UI werden im selben nginx vHost laufen, können aber auch getrennt werden, hierzu sind weitere Einstellungen nötig, siehe Hinweise.

Zusätzlich kann das Frontend und die Registry mit Basic Auth geschützt werden, dies wird ebenfalls konfiguriert.

Konfiguration

Docker

Die REGISTRY_URL muss auf den Hostname, unter dem die Registry erreichbar ist, gesetzt werden (z.B. myregistry.mydomain.com).
Ob Images gelöscht werden können, stellt REGISTRY_STORAGE_DELETE_ENABLED sicher, das Frontend bietet die Option an, wenn DELETE_IMAGES gesetzt ist.
Weil die Aktionen selbst vom Browser ausgeführt werden und nicht vom Frontend, sind beide Einstellungen auf true zu setzen.

Weitere Einstellungen können der Doku entnommen werden: https://github.com/Joxit/docker-registry-ui

Docker Compose
version: "3"
services:
  registry:
    container_name: registry
    image: registry:2
    restart: unless-stopped
    volumes:
      - /srv/docker/registry/data/:/var/lib/registry
    environment:
      REGISTRY_STORAGE_DELETE_ENABLED: "true"
    networks:
      default:
        ipv4_address: 172.16.2.2

  registry-ui:
    container_name: registry-ui
    image: joxit/docker-registry-ui:latest
    restart: unless-stopped
    environment:
      REGISTRY_URL: "https://<REGISTRY_HOSTNAME>"
      SINGLE_REGISTRY: "true"
      DELETE_IMAGES: "true"
    networks:
      default:
        ipv4_address: 172.16.2.3

networks:
  default:
    driver: bridge
    ipam:
      driver: default
      config:
        - subnet: 172.16.2.0/24
    driver_opts:
      com.docker.network.bridge.name: br_registry

Danach können die Container gestartet werden.

HTTP Proxy (nginx)

Hier kann auch Apache, Traefik oder ein anderer Proxy der persönlichen Wahl genutzt werden.
Nginx ist aber bekannt und deswegen hier die spezifische Konfiguration für die beiden Dienste.
Um die Konfiguration einfach darstellen zu können, verzichte ich auf den ganzen Binding, SSL, Logging Overhead (siehe TODO)!

Basic Auth File generieren

Hier gibt es verschiedene Wege, Beispiele findest du hier: Anleitung zu Salted Password Hashes

Der Inhalt muss dann in die Datei /etc/nginx/htpasswd/myregistry.htpasswd gepackt werden und hat den folgenden Syntax:

username:<PASSWORD_HASH>
test:<PASSWORD_HASH>
# siehe add_header 'Docker-Distribution-Api-Version'
map $upstream_http_docker_distribution_api_version $docker_distribution_api_version {
    '' 'registry/2.0';
}

server {

    ################## TODO ###############################
    ADD NGINX CONFIGURATION (LISTEN, LOG, SERVERNAME, ....)
    #######################################################


    # disable any limits to avoid HTTP 413 for large image uploads
    client_max_body_size 0;

    # required to avoid HTTP 411: see Issue #1486 (https://github.com/moby/moby/issues/1486)
    chunked_transfer_encoding on;

    auth_basic              "Registry realm";
    auth_basic_user_file    /etc/nginx/htpasswd/myregistry.htpasswd;

    location / {
        proxy_pass          http://172.16.2.3:80;
        proxy_set_header    Host                $http_host;
        proxy_set_header    X-Real-IP           $remote_addr;
        proxy_set_header    X-Forwarded-For     $proxy_add_x_forwarded_for;
        proxy_set_header    X-Forwarded-Proto   $scheme;
        proxy_read_timeout  900;
    }

    location /v2/ {
        # Do not allow connections from docker 1.5 and earlier
        # docker pre-1.6.0 did not properly set the user agent on ping, catch "Go *" user agents
        if ($http_user_agent ~ "^(docker\/1\.(3|4|5(?!\.[0-9]-dev))|Go ).*$" ) {
            return 404;
        }
        # To add basic authentication to v2 use auth_basic setting.
        satisfy any;
        # disable basic auth for specific IP
        # allow x.x.x.x;

        ## If $docker_distribution_api_version is empty, the header is not added.
        ## See the map directive above where this variable is defined.
        add_header 'Docker-Distribution-Api-Version' $docker_distribution_api_version always;

        proxy_pass                              http:/172.16.2.3:5000;
        proxy_set_header    Host                $http_host;  # required for docker client's sake
        proxy_set_header    X-Real-IP           $remote_addr; # pass on real client's IP
        proxy_set_header    X-Forwarded-For     $proxy_add_x_forwarded_for;
        proxy_set_header    X-Forwarded-Proto   $scheme;
        proxy_read_timeout  900;
    }
}

Zugriff

Wenn man sich das Passwort gemerkt hat (…), sollte ein Login möglich sein:

docker login myregistry.domain.com

Der Login kann auch automatisch passieren, wenn in der Datei PASSWORD_FILE das Klartextpasswort steht:

cat PASSWORD_FILE | docker login --username testuser

Hinweise

unterschiedliche Hostnames für Registry & Frontend

Soll das Frontend unter einer anderen URL als die Registry verfügbar gemacht werden, kann es nötig sein CORS Einstellungenin nginx vorzunehmen um dem Browser gültige URLs für API Calls mitzugeben. Links zum Thema:


Subsections of Hashicorp Vault

HashiCorp Vault mit Client Config

Hinweis

Tokens und Zugangsdaten sind unbedingt sicher aufzubewahren. Verliert man diese, kann die Vault unbrauchbar werden und die Daten verloren gehen!

Konfiguration

Docker

Zuerst legen wir die Ordnerstruktur an:

mkdir -p /srv/docker/vault/config
mkdir -p /srv/docker/vault/file
mkdir -p /srv/docker/vault/logs

Danach benötigen wir typischerweise ein docker-compose.yml

version: "3"
services:
  vault:
    image: hashicorp/vault
    container_name: vault
    volumes:
      - /srv/docker/vault/config:/vault/config
      - /srv/docker/vault/file:/vault/file
      - /srv/docker/vault/logs:/vault/logs
    cap_add:
      - IPC_LOCK
    restart: unless-stopped
    networks:
      default:
        ipv4_address: 172.16.3.2
    command: server

networks:
  default:
    driver: bridge
    ipam:
      driver: default
      config:
        - subnet: 172.16.3.0/24
    driver_opts:
      com.docker.network.bridge.name: br_vault

Und dazu noch die Vault Config:

ui            = true
api_addr      = "http://0.0.0.0:8200"
disable_mlock = true

storage "file" {
  path = "/vault/file"
}

listener "tcp" {
  address       = "0.0.0.0:8200"
  tls_disable = 1
}

Danach kann der Container gestartet werden:

docker-compose up -d

Vault Ersteinrichtung mit CLI

Wenn die Vault gestartet wurde und erreichbar ist, muss die Einrichtung einmalig durchgeführt werden.
Hierzu kann man den CLI Client nutzen und die URL des Containers temporär setzen.

Schlüsselmenge

Mit -key-shares= gibt man die Menge der Schlüssel an die man erzeugen lassen möchte und mit -key-threshold= wie viele benötigt werden um beim Neustart die Vault zu öffnen. Beispiel: 10 Nutzer, von denen mindestens 3 benötigt werden um die Vault zu öffnen: -key-shares=10 -key-threshold=3

Sind nicht genügend Schlüssel zum öffnen verfügbar (können über den Browser eingegeben werden), bleibt die Vault dicht!

Schlüsselmenge

Ich kann es nicht oft genug erwähnen: SCHLÜSSEL / TOKEN SICHERN

export VAULT_ADDR=http://172.16.3.2:8200
vault operator init -key-shares=1 -key-threshold=1

Unseal Key 1: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Initial Root Token: hvs.xxxxxxxxxxxxxxxxxxxxx

# MAN BEACHTE:
Vault does not store the generated root key. Without at least 1 keys to
reconstruct the root key, Vault will remain permanently sealed!

It is possible to generate new unseal keys, provided you have a quorum of
existing unseal keys shares. See "vault operator rekey" for more information.

Nginx

Wie so häufig wird nginx als Proxy davor gestellt, hierfür ist keine große Konfiguration nötig und es kann eine einfache Proxy-Config genutzt werden:

server {

  ######################################
  # NGINX vhost listen, logs, ssl, ...
  ######################################

  # OPTIONALER Schutz um die Vault mit vorangehendem Basic Auth zu schützen
  #auth_basic              "Vault realm";
  #auth_basic_user_file    /etc/nginx/htpasswd/vault.htpasswd;

  location / {
      proxy_pass                              http://172.16.3.2:8200;
      proxy_set_header    X-Real-IP           $remote_addr; # pass on real client's IP
      proxy_set_header    X-Forwarded-For     $proxy_add_x_forwarded_for;
      proxy_set_header    X-Forwarded-Proto   $scheme;
      proxy_read_timeout  900;
  }

Danach kann die Nginx Config getestet und geladen werden:

nginx -t && nginx -s reload

Konfiguration Vault Adresse im Client

Nachdem die Vault über Nginx erreichbar gemacht wurde, kann die Adresse des Clients angepasst werden:

ohne Basic Auth
export VAULT_ADDR=https://vault.mydomain.com
lokal auf dem Docker Host

Falls man nicht über Nginx gehen möchte …

export VAULT_ADDR=http://172.16.3.2
Mit zusätzlichem Basic Auth
export VAULT_ADDR=https://USERNAME:PASSWORD@vault.mydomain.com

Login & Tests

Um die Anleitung nicht zu kompliziert werden zu lassen, sollen verschiedene Authentifizierungsverfahren hier nicht beleuchtet werden.
Der Standardlogin mit gerade erzeugtem Root-Token funktioniert wie folgt:

vault login
Token (will be hidden): <HIER TOKEN (beginnt mit hvs.) EINGEBEN>
Vault Status prüfen

Die Vault ist aktuell noch verschlossen (Sealed)

vault status

Key                Value
---                -----
Initialized        true
Sealed             true
Total Shares       1
Threshold          1
Unseal Progress    0/1
Unseal Nonce       n/a
Vault öffnen

Öffnen der Vault mit X Schlüsseln

vault operator unseal
Unseal Key (will be hidden): <UNSEAL TOKEN EINGEBEN>

Key             Value
---             -----
Sealed          false

Let's Encrypt Zertifikate mit Vault synchronisieren

Hinweis

Die Scripts benötigen einen gültigen Vault Token (siehe Vorbereitung)! Außerdem muss ein KV-Secret Store eingerichtet sein. In diesem Beispiel lautet der Secret-Store certbot.

Vorbereitung

Installation Komponenten

Für das Script werden JQ und Vault (CLI Client) benötigt:

pacman -Syu vault jq

Offizielle Anleitung: https://developer.hashicorp.com/vault/tutorials/getting-started/getting-started-install

wget -O- https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg
gpg --no-default-keyring --keyring /usr/share/keyrings/hashicorp-archive-keyring.gpg --fingerprint
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list
sudo apt update
sudo apt install vault jq
Vault Login

Durch den Login wird der Token in die Datei /root/.vault-token geschrieben. Dieser wird von den Scripts und den weiteren Vorbereitungen benötigt.

export VAULT_ADDR=https://vault.domain.com
vault login
KV Store

Vault muss einen KV-Store bereitstellen, dieser lautet in den Scripts certbot. Ist ein anderer Namen gewünscht, müssen alle Pfade angepasst werden.

Nun wird der Secret Store angelegt und die Rotation von maximal 7 Versionen eingestellt, wodurch nur insgesamt 7 Versionen behalten werden und ältere automatisch gelöscht werden.

vault secrets enable -path=certbot kv
vault write certbot/config max_versions=7

Cert Download

Script zum downloaden der Zertifikate

Script auf dem Server anlegen /usr/local/sbin/update_cert.sh:

#!/bin/bash

# cancel if any error occurs
set -e

if [[ $# -ne 1 ]];then
    echo "usage: $0 DOMAIN"
    exit 1
fi
DOMAIN="${1}"
CERT_DIR="/etc/nginx/ssl/${DOMAIN}/"

export VAULT_ADDR

# load certificate, chain + key from vault
json=$(vault kv get -format=json certbot/certificates/${DOMAIN})

mkdir -p ${CERT_DIR}

# join cert + chain -> (fullchain)
jq -r '.data.data.cert + .data.data.chain' <(echo $json) > ${CERT_DIR}/fullchain.pem
jq -r '.data.data.privkey' <(echo $json) > ${CERT_DIR}/privkey.pem

# OPTIONAL: reload nginx if config is okay
#nginx -t && nginx -s reload

Berechtigung korrigieren, damit das Script ausführbar ist

chown -R root:root /usr/local/sbin/update_cert.sh
chmod 700 /usr/local/sbin/update_cert.sh

Cronjob / Systemd Timer

Unbedingt den Hostname vault.domain.com anpassen!

Cronjob
crontab -e
0 2 * * * VAULT_ADDR=https://vault.domain.com /usr/local/sbin/update_cert.sh cert.mydomain.com
1 2 * * * VAULT_ADDR=https://vault.domain.com /usr/local/sbin/update_cert.sh mydomain.com
Systemd Unit / Timer

Service Unit /etc/systemd/system/cert_update@.service

[Unit]
Description=Download Certbot files
Requires=network-online.target
After=network-online.target

[Service]
Type=oneshot
ExecStart=/usr/local/sbin/update_cert.sh %i
Environment="VAULT_ADDR=https://vault.domain.com"

Service Timer /etc/systemd/system/cert_update@.timer

[Unit]
Description=Daily renewal of cert

[Timer]
OnCalendar=01:00:00
Persistent=true
RandomizedDelaySec=60

[Install]
WantedBy=timers.target

Danach kann der Timer für jedes Zertifikat aktiviert werden:

systemctl daemon-reload
systemctl enable --now cert_update@my-domain.com.timer
systemctl enable --now cert_update@cert2.my-domain.com.timer

Cert Upload

Script zum uploaden der Zertifikate

Das Script kann mehrere Zertifikate mit einem Aufruf verarbeiten, diese müssen lediglich als Parameter übergeben werden.
In diesem Beispiel wird allerdings für jedes Zertifikat ein eigener Cronjob / Systemd-Timer angelegt, geht aber auch anders …

Das Script unter /usr/local/sbin/push_certs_to_vault.sh anlegen:

#!/bin/bash

export VAULT_ADDR
# script supports multiple domains via parameter  
DOMAINS="$*"

for domain in ${DOMAINS};do
    vault kv put "certbot/certificates/${domain}" "cert=@/etc/letsencrypt/live/${domain}/cert.pem" "chain=@/etc/letsencrypt/live/${domain}/chain.pem" "privkey=@/etc/letsencrypt/live/${domain}/privkey.pem"
done

Berechtigung korrigieren, damit das Scripts ausführbar ist:

chown -R root:root /usr/local/sbin/push_certs_to_vault.sh
chmod 700 /usr/local/sbin/push_certs_to_vault.sh
Cronjob

Unbedingt den Hostname vault.domain.com anpassen!

crontab -e
0 2 * * * VAULT_ADDR=https://vault.domain.com /usr/local/sbin/update_cert.sh cert.mydomain.com
1 2 * * * VAULT_ADDR=https://vault.domain.com /usr/local/sbin/update_cert.sh mydomain.com
Systemd Unit / Timer

Service Unit /etc/systemd/system/push_cert@.service

[Unit]
Description=Upload Certbot files
Requires=network-online.target
After=network-online.target

[Service]
Type=oneshot
ExecStart=/usr/local/sbin/push_certs_to_vault.sh %i
Environment="VAULT_ADDR=https://vault.domain.com"

Service Timer /etc/systemd/system/push_cert@.timer

[Unit]
Description=Daily upload of certs

[Timer]
OnCalendar=00:00:00
Persistent=true
RandomizedDelaySec=30

[Install]
WantedBy=timers.target

Danach kann der Timer für jedes Zertifikat aktiviert werden:

systemctl daemon-reload
systemctl enable --now push_cert@my-domain.com.timer
systemctl enable --now push_cert@cert2.my-domain.com.timer

Anmerkung

Wieso das Script nicht als Certbot Hook eingebunden wird

Während des Cert Renewals muss die Vault verfügbar sein. Ist dies nicht gegeben, würde der Upload nicht funktionieren und auch nicht nachgeholt.
Deshalb habe ich mich dazu entschieden diese einfach täglich hochzuladen.
In Vault werden inerhalb der Secret Engine rotierte Inhalte gelöscht, sodass der tägliche Upload nicht problematisch ist und man Gefahr läuft die Vault vollzumüllen.

Wenn die Vault zusätzlich mit Basic Auth geschützt ist

Wie in der Anleitung zur Einrichtung beschrieben, muss hier nur die URL ergänzt werden.
Aus VAULT_ADDR=https://vault.domain.com wird dann VAULT_ADDR=https://USERNAME:PASSWORD@vault.domain.com.


Subsections of Mailu

Mails über Relayhost versenden

Hinweis

Falsche Konfigurationen, oder die Wahl eines unpassenden Relayhosts können zu Problemen beim Versand, oder Empfang durch Dritte führen!

Vorwort

Um E-Mails über einen externen Server zu versenden, bietet Mailu native Umgebungsvariablen für den SMTP Docker-Container an.
Mit diesen kann festgelegt werden über welchen Server versendet werden soll und ob eine Authentifizierung erfolgen muss.

allgemeiner Versand via Relayhost

# File: mailu.env

# Adresse / IP des Relay Hosts
RELAYHOST=1.2.3.4

# user:password
RELAYUSER=user@my-domain.de:mein_passwort

Manche Hoster nutzen auch keine E-Mailadresse zum Authentifizieren
RELAYUSER=postfach1234:mein_passwort

In diesem Beispiel werden alle E-Mails die nicht lokal zugetellt werden können über den Relay-Host versendet!

bestimmte Ziele per Relay

In manchen Fällen möchte man nur bestimmte Domains über einen Relay versenden, weil ein Hoster E-Mails von der eigenen IP-Adresse nicht akzeptiert. In diesem Fall funktioniert die Anleitung oben nicht, sondern man muss etwas mehr konfigurieren.

Folgende Schritte sind notwendig

  1. Postfix Map mit Transport pro Domain und Relay Host konfigurieren
  2. Postfix zum nutzen der Map konfigurieren
  3. Authentifizierung hinterlegen (falls notwendig)
#File: postfix.cf

# Transport-Ziele in transport.map hinterlegen
transport_maps =  hash:/etc/postfix/transport.map

# Passwort pro Relay-Server
smtp_sasl_password_maps = hash:/etc/postfix/sasl_passwd.map

# Authentifizierung aktivieren
smtp_sasl_auth_enable = yes

# deaktivieren von verschlüsselten Passwörtern, die Transport-Verschlüsselung ist aktiviert, Passwörter müssen oft im Klartext genutzt werden
smtp_sasl_security_options = noanonymous
# File: transport.map

t-online.de smtp:irgend.ein.smtp-server.com
test.de     smtp:1.2.3.4
# File: sasl_passwd.map

irgend.ein.smtp-server.com  myuser@smtp-xy.com:mein_p4ssword
1.2.3.4                     postfachx:super_sicher!

Nach der Konfiguration

Im Anschluss muss der SMTP Container neugestartet werden, oder sogar neu erstellt werden, falls die Option mit Docker Variablen genutzt wurde:

# Fall: Docker Variablen, Neubau des Containers
docker-compose up -d

# Fall: postfix.cf und Maps
docker-compose restart smtp

Mailu individuell konfigurieren

Hinweis

Falsche Konfigurationen können zu Problemen beim Versand, oder Empfang durch Dritte führen!

Diese Anleitung wurde ursprünglich für Version 1.9 geschrieben und für 2.0 aktualisiert.
Ich möchte aber nicht ausschließen, dass ich etwas vergessen habe. Augen auf bei der Konfiguration!

Hinweis

Die Anleitung geht davon aus, dass Mailus Daten unter /srv/docker/mailu/data/ liegen.
Das ist kein muss, sollte nur bei Abweichungen berücksichtigt werden!

Vorwort

Mailu ist eine komplette E-Mailserver-Lösung basierend auf Docker. Hierbei werden alle nötigen Funktionen wie zum Beispiel SMTP (Postfix) , IMAP / POP3 (Dovecot), Filterregeln (Sieve), Antispam (Rspamd), Antivirus (ClamAV), Webmail (Roundcube), Fetchmail und auch ein modernes Verwaltungstool (Mailu Admin) bereitgestellt.
Alle Dienste laufen in eigenen Containern und kommunizieren über das Netzwerk miteinander. Weitere Details werden auf der Projektseite aufgezeigt.

Im Standardfall wird Mailu bereits durchaus gut und sinnvoll konfiguriert, womit die größten Anwendungsfälle bereits im Vorfeld abgedeckt sind. Allerdings ist der Wille nach nach individueller Konfiguration durchaus gerechtfertigt und darum soll es in diesem Artikel gehen.
Wie werden einzelne Dienste von Mailu konfiguriert, welche Möglichkeiten gibt es und was gibt es zu beachten.

Allgemeines zur Konfiguration

Die meisten Einstellungen die Mailu bereits unterstützt werden durch Umgebungsvariablen gesetzt und können hier eingesehen werden:

https://mailu.io/2.0/configuration.html

Für weitergehende Einstellungen die hierdurch nicht abgedeckt sind, bietet Mailu ein sehr gut nutzbares Verfahren von Überschreibungen an. Hierfür wird in den Docker-Containern ein Overrides Ordner angezogen. Je nach Containerart kann dies ggf. ein unterschiedlicher Pfad sein. Hierzu möchte ich im folgenden die einzelnen Container und Möglichkeiten der Konfiguration kurz und bündig beschreiben. Grundsätzlich finden sich alle override Verzeichnisse unterhalb von data.
Liegt das Data Verzeichnis zum Beispiel unter /srv/docker/mailu/data/, finden sich die override hier: /srv/docker/mailu/data/overrides.
Die Container mit override Support sind im docker-compose.yml File mit einem Mountpoint versehen…

Konfiguration der Komponenten

Redis

Weil es sich um das Standard Alpine Docker Image von Redis handelt, kann eine individuelle Konfigurationsdatei als Startparameter angegeben werden. Der Pfad ist hierbei frei zu wählen und die Datei muss noch eingehängt werden.

Dovecot (IMAP / POP3)

Pfad: /srv/docker/mailu/data/overrides/dovecot/dovecot.conf

Über die Datei /overrides/dovecot.conf können Anpassungen vorgenommen werden.
In der Standard-Konfigurationsdatei /etc/dovecot/dovecot.conf wird durch die unten stehende Zeile versucht die override Datei einzulesen. Existiert diese nicht, gibt es auch keinen Fehler.

!include_try /overrides/dovecot.conf

Postfix (SMTP)

Pfad: /srv/docker/mailu/data/overrides/postfix/

In Postfix können alle Einstellungen über den Ordner /overrides vorgenommen werden. Hierbei werden Einstellungen aus den Dateien /overrides/postfix.cf und /overrides/postfix.cf gelesen und mittels “postconf -e” gesetzt:

# File: /start.py

if os.path.exists("/overrides/postfix.cf"):
    for line in open("/overrides/postfix.cf").read().strip().split("\n"):
        os.system('postconf -e "{}"'.format(line))

if os.path.exists("/overrides/postfix.master"):
    for line in open("/overrides/postfix.master").read().strip().split("\n"):
        os.system('postconf -e "{}"'.format(line))

Zusätzlich werden im /overrides Ordner alle Maps mittels postmap kompiliert. Somit können Maps ebenfalls dort abgelegt werden, müssen lediglich die Endung .map im Dateinamen haben. Beim Start werden diese dann verarbeitet:

for map_file in glob.glob("/overrides/*.map"):
    destination = os.path.join("/etc/postfix", os.path.basename(map_file))
    shutil.copyfile(map_file, destination)
    os.system("postmap {}".format(destination))
    os.remove(destination)

Alle Maps bekommen nach der Bearbeitung eine .db-Endung, allerdings müssen die Dateien in der postfix.cf ohne die .db-Endung konfiguriert werden. Hier ein Beispiel:

# File: postfix.cf

transport_maps =  hash:/etc/postfix/transport.map
smtp_sasl_password_maps = hash:/etc/postfix/sasl_passwd.map

smtp_sasl_auth_enable = yes
smtp_sasl_security_options = noanonymous

Rspamd

Pfad: /srv/docker/mailu/data/overrides/rspamd

Im override Ordner können Dateien abgelegt werden, welche zum Containerstart vom start.py Script nach /etc/rspamd/local.d kopiert werden.

Clamav

Hier gibt es keine Überschreibungsoptionen, allerdings können die Dateien freshclam.conf und clamd.conf des Ordners /etc/clamav überschrieben (gemountet) werden. Hier die Schritte wie es zum Beispiel gemacht werden kann:

Datei laden und editieren
# Datei aus dem Container laden
mkdir -p /srv/mailu/override/antivirus/clamd.conf
docker cp mailu_antivirus_1:/etc/clamav/clamd.conf /srv/mailu/override/antivirus/

# Anpassungen vornehmen
vim clamd.conf

In der docker-compose.yml muss die Datei nun hinterlegt werden:

  antivirus:
    ...
    volumes:
      - "/srv/mailu/data/filter:/data"
      - "/srv/mailu/override/antivirus/clamd.conf:/etc/clamav/clamd.conf"

Ein Neuerstellen des Containers ist notwendig!

Webmail (Roundcube)

Pfad: /srv/docker/mailu/data/overrides/roundcube

Roundcube ist nicht verpflichtend, aber hat man diesen Webmailer gewählt, können Einstellungen vorgenommen werden, indem die folgenden Dateien kopiert, editiert und eingehangen (mounted) werden:

/php.ini
/var/www/html/config/config.inc.php

Nachwort zur Konfiguration

Hinweis

Wenn man sich dazu entscheidet, Konfigurationsdateien aus den Containern zu kopieren und im Anschluss zu überlagern (mounten), kann dies zu Fehlern, oder nachgelagerten Problemen führen, weil sich das Verhalten der Container ändert.

Anpassungen die im Upstream Image passieren, können dann in den eigenen Dateien nicht mehr enthalten sein. Dies betrifft vor allem wichtige Sicherheitseinstellungen. Deshalb hier ein möglicher Weg damit umzugehen, der bei jedem Update (Image-Update) durchgeführt werden sollte:

  • Update der Container (docker-compose pull && docker-compose up -d)
  • Kopieren der Dateien aus den Containern
  • Anhängen neuer Einstellungen oder Überschreiben der Optionen
  • Einhängen der neuen Dateien durch Neustart der Docker-Container

Docker Compose anpassen

Mit einer docker-compose.override.yml können alle Werte des originalen Compose Files überschrieben / angepasst werden.
Dies ist dann sinnvoll, wenn man beispielsweise die Ports vom Front Container entfernen, Volumes an Container hängen möchte oder die IP-Adressen / Netze der Container statisch setzen.

Ein denkbares Beispiel könnte sein, dass der Front Container eine feste IP-Adresse erhalten soll, damit ein vorgelagerter Nginx eine feste IP-Adresse zum verbinden kennt.

Deshalb hier ein Beispiel Override File. Das Subnetz des default Netzes steht bereits im docker-compose.yml File (10.33.0.0/24), aber die anderen bridges (webmail, noinet sollen erweitert werden)

version: "3.4"

services:
  front:
    # remove uneeded port bindings
    ports: !reset []
    networks:
      default:
        ipv4_address: 10.33.0.253


# set static ip addresses / static bridge names
networks:
  default:
    driver_opts:
      com.docker.network.bridge.name: br_mailu
  webmail:
    driver: bridge
    ipam:
      driver: default
      config:
        - subnet: 10.33.1.0/24
    driver_opts:
      com.docker.network.bridge.name: br_mailu_web
  noinet:
    driver: bridge
    internal: true
    ipam:
      driver: default
      config:
        - subnet: 10.33.2.0/24
    driver_opts:
      com.docker.network.bridge.name: br_mailu_noinet

Beim Hochfahren werden die docker-compose.yml und docker-compose.override.yml zusammengeführt (merged).
Da Listenelemente wie die Ports nur ergänzt, aber nicht entfernt werden können, gibt es in neueren Compose versionen die !reset Funktion. Nähere Informationen finden sich hier: https://docs.docker.com/compose/compose-file/13-merge/#reset-value

Setup-Tool zum genrieren eines Docker-Compose Files für Mailu https://setup.mailu.io/2.0/setup/

Dokumentation von Mailu https://mailu.io/2.0/

Github Projekt https://github.com/Mailu/Mailu


Subsections of Minio

Minio Single Node Installation

Vorwort

Die hier gezeigte Anleitung ist rudimentär und kann nicht den kompletten Funktionsumfang von Minio abdecken.
Ich werde nach und nach weitere Beispiele hinzufügen, gerade im Hinblick auf Policies für Backups, oder Bereitstellung statischer Dateien / Webseiten.

Hinweis

Docker / Compose solle bereits installiert sein! Zusätzlich wird in dieser Anleitung der Traffic durch einen Nginx geleitet, dies ist aber kein muss.
Wird Minio direkt erreichbar gemacht werden (ohne Proxy davor), sollte die Transport-Verschlüsselung von Minio bereitgestellt werden. Hierfür muss Minio Zertifikate mitgeteilt bekommen (nicht Bestandteil der Anleitung)

Docker Compose

Der Pfad /mnt/data/minio/data muss noch den lokalen Gegebenheiten angepasst werden!

version: "3"
services:
  minio:
    user: "1000:1000"
    image: quay.io/minio/minio
    container_name: minio
    environment:
      MINIO_ROOT_USER_FILE: "/run/secrets/minio_root_user"
      MINIO_ROOT_PASSWORD_FILE: "/run/secrets/minio_root_password"
    volumes:
    - /mnt/data/minio/data:/data
    restart: unless-stopped
    command: "server /data --console-address :9001"
    secrets:
    - minio_root_user
    - minio_root_password
    networks:
      default:
        ipv4_address: 172.55.1.2
    # optionales Port Forwarding, FALLS kein Firewall Forwarding konfiguriert werden soll
    ports:
    - 9001:9001
    - 9000:9000

networks:
  default:
    driver: bridge
    ipam:
      driver: default
      config:
        - subnet: 172.55.1.0/24
    driver_opts:
      com.docker.network.bridge.name: br_minio

secrets:
   minio_root_user:
     file: ./secrets/minio_root_user
   minio_root_password:
     file: ./secrets/minio_root_password

Nun werden die Secrets in Dateien abgelegt, das Passwort sollte angepasst werden und sicher sein!

USERNAME=minio
PASSWORD=$(pwgen -s 32 1)

mkdir -p secrets
cat - > secrets/minio_root_user <<EOF
${USERNAME}
EOF

cat - > secrets/minio_root_password <<EOF
${PASSWORD}
EOF

Nginx Konfiguration

Die offizielle Dokumentation ist an manchen stellen etwas wild und nach verschiedenen Loginproblemen habe ich für mich entschieden, die Konfiguration etwas abweichend vom Standard vorzunehmen. Die Dokumentation schlägt vor, eine pfadbasierte Unterscheidung zwischen S3 und Minio Admin Panel zu konfigurieren.
Dies hat bei mir und vielen anderen Benutzern auch nicht geklappt und sorgte für Probleme, weshalb ich 2 vhosts in nginx konfiguriere:

Hinweis

Die unten stehenden Snippets können nicht ohne Anpassung vorgenommen werden!
Es fehlen Einstellungen wie listen, SSL Zertifikat, Logs und andere sicherheitsrelevante Einstellungen.
Einfaches kopieren wäre copy&waste…

S3 vhost
server {
  server_name s3.domain.com

  #######################
  # listen, ssl, logs, etc pp
  #######################

  # Allow special characters in headers
  ignore_invalid_headers off;

  # Allow any size file to be uploaded.
  # Set to a value such as 1000m; to restrict file size to a specific value
  client_max_body_size 0;

  # Disable buffering
  proxy_buffering off;
  proxy_request_buffering off;

  # bestimmte IPs zulassen
  # allow   192.168.201.1;
  # deny all;

  location / {
    proxy_set_header Host $http_host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;

    proxy_connect_timeout 300;

    # Default is HTTP/1, keepalive is only enabled in HTTP/1.1
    proxy_http_version 1.1;
    proxy_set_header Connection "";
    chunked_transfer_encoding off;

    proxy_pass http://172.55.1.2:9000; # This uses the upstream directive definition to load balance
  }
}
Minio Admin Panel
server {
  server_name         minio.domain.com

  #######################
  # listen, ssl, logs, etc pp
  #######################

  location / {
    proxy_set_header Host                           $http_host;
    proxy_set_header X-Real-IP                      $remote_addr;
    proxy_set_header X-Forwarded-For        $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto      $scheme;
    proxy_set_header X-NginX-Proxy          true;

    # This is necessary to pass the correct IP to be hashed
    real_ip_header X-Real-IP;

    proxy_connect_timeout 300;

    # To support websocket
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";

    chunked_transfer_encoding off;

    proxy_pass http://172.55.1.2:9001; # This uses the upstream directive definition to load balance
  }
}

Nach einem Nginx Reload / Restart kann Minio aufgerufen werden:

Policies

Vollzugriff auf Bucket-Namen wie Username

Hier kann der Benutzer nur auf Buckets zugreifen die wie sein Username lautet ${aws:username}.
Dieses Muster kann grundsätzlich auf verschiedene Weise genutzt werden, ist hier aber stark vereinfacht dargestellt:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "User",
            "Effect": "Allow",
            "Action": [
                "s3:*"
            ],
            "Resource": [
                "arn:aws:s3:::${aws:username}",
                "arn:aws:s3:::${aws:username}/*"
            ]
        }
    ]
}
Append Only (Restic)

Diese Regeln sorgen dafür, dass Restic (Backup Software) nur Daten anhängen und lesen darf.
Der Bucket muss dem Usernamen entsprechen:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "DeleteLocks",
            "Effect": "Allow",
            "Action": [
                "s3:DeleteObject"
            ],
            "Resource": [
                "arn:aws:s3:::${aws:username}/locks/*"
            ]
        },
        {
            "Sid": "AllowListings",
            "Effect": "Allow",
            "Action": [
                "s3:GetBucketLocation",
                "s3:ListBucket"
            ],
            "Resource": [
                "arn:aws:s3:::${aws:username}"
            ]
        },
        {
            "Sid": "User",
            "Effect": "Allow",
            "Action": [
                "s3:GetObject",
                "s3:PutObject"
            ],
            "Resource": [
                "arn:aws:s3:::${aws:username}/*"
            ]
        }
    ]
}

Subsections of Nextcloud

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.


Migration Nextcloud FPM Image zu LinuxServerIO

Vorbereitung

Hinweis

Backups, Backups, Backups… Die Datenbank sollte ebenfalls gesichert werden (mysqldump / mariadb-dump / psqldump).

Zusätzlich empfiehlt es sich, Nextcloud auf die gleiche Version des LSIO-Images zu bringen. Ermitteln kann man die aktuelle Version mit Auslesen der version.php.

Beispiel:

grep OC_VersionString /srv/docker/nextcloud.old/html/version.php
$OC_VersionString = '28.0.2';

Passt die Version nicht, sollte vorher ein Update gemacht werden. Grundsätzlich unterstützt das LSIO Image ein Upgrade, aber weil es sich um eine “Neuinstallation” mit Migration handelt, kann es hier zu Problemen kommen…

Docker Compose

Aufgrund der vereinfachten Struktur des LinuxServer.IO Images kann das Docker Compose File leicht aufgebaut werden und die Daten hinein migriert werden.
Sollten eigene Pfade oder Anpassungen vorgenommen worden sein (zusätzliche Mounts, Konfigurationsdateien) müssen diese entweder hinein kopiert werden, oder wieder gemountet werden.
Das custom_apps Verzeichnis, welches für installierte Apps genutzt wird, dient uns hier als Vorlage…

version: '3'

services:
  nextcloud:
    image: lscr.io/linuxserver/nextcloud:28.0.2
    container_name: nextcloud_app
    environment:
      PUID: 1000
      PGID: 1000
      TZ: "Etc/UTC"
    volumes:
      - /srv/docker/nextcloud/config/:/config
      - /srv/docker/nextcloud/data:/data
      # zusätzlicher Mountpunkt für Apps die vom Standardset abweichen
      - /srv/docker/nextcloud/custom/custom_apps:/app/www/public/custom_apps
    restart: unless-stopped
    networks:
      default:
        ipv4_address: 10.11.12.254

networks:
  default:
    driver: bridge
    ipam:
      driver: default
      config:
        - subnet: 10.11.12.0/24
    driver_opts:
      com.docker.network.bridge.name: br_nextcloud

Danach kann der Container erstmalig gestartet werden, damit die Verzeichnisse angelegt werden.
Hierbei werden Einrichtungen erledigt wie:

  • Log Ordner
  • Nextcloud SSL Zertifikate (Container spricht HTTPS)
  • Nextcloud Verzeichnisstruktur
  • Config Files

Nachdem der Container gestartet wurde, kann dieser wieder gestoppt werden, damit wir die Daten migrieren können.

Migration

Config
cp /srv/docker/nextcloud.old/html/config/config.php /srv/docker/nextcloud/config/www/nextcloud/config/config.php

Danach müssen noch Anpassungen vorgenommen werden:

$CONFIG = array (
  'maintenance_window_start' => 1,
  array (
    0 =>
    // Standardpfad fuer Apps, readonly
    array (
      'path' => '/app/www/public/apps',
      'url' => '/apps',
      'writable' => false,
    ),
    // custom_apps vom Zusatzmountpoint, schreibbar
    1 =>
    array (
      'path' => '/app/www/public/custom_apps',
      'url' => '/custom_apps',
      'writable' => true,
    ),
  ),

Eventuell ist es auch nötig aus den anderen Configfiles Inhalte zu übernehmen, weil das Nextcloud Image zusätzliche Dateien anbietet und Werte aus dem Environment zieht:

  • apcu.config.php
  • apps.config.php
  • autoconfig.php
  • config.php
  • redis.config.php
  • reverse-proxy.config.php
  • s3.config.php
  • smtp.config.php
  • swift.config.php
  • test.config.php

Hier hängt es stark von deiner Konfiguration ab, ob zusätzliche Einstellungen in der config.php hinterlegt werden müssen.
Am besten im docker-compose.yml die Variablen mit den Dateien abgleichen, falls Environment-Variablen genutzt wurden…

Datenmigration

Hier werden die Dateien übernommen, es bietet sich an die original Cloud herunterzufahren, damit keine Änderungen geschrieben werden.
Auch hier gilt, sind weitere Ordner nötig, müssen diese auch synchronisiert werden.

rsync -aH /srv/docker/nextcloud.old/html/data/ /srv/docker/nextcloud/data/
rsync -aH /srv/docker/nextcloud.old/html/custom_apps/ /srv/docker/nextcloud/custom/custom_apps/
chown -R 1000:1000 /srv/docker/nextcloud/data/ /srv/docker/nextcloud/custom/custom_apps/
Nginx Config

Die Konfiguration eines Proxies gestaltet sich im Vergleich zum FPM Image nun deutlich einfacher

server {
  ...
  ...
  ...

  location / {
    # man beachte das https://, denn der Container spricht selbst HTTPS
    # nutzt man hier http://, wird die Cloud dies beim selfcheck bemängeln...
    proxy_pass https://10.11.12.254:443;
    proxy_http_version 1.1;

    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header X-Forwarded-Host $host;

    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "Upgrade";
  }

Gibt es beim Reload einen Fehler beim $http_upgrade, fehlt vermutlich die Map dafür.
Diese kann einfach in der nginx.conf hinterlegt werden:

  http {
    ...
    ...
    ...
      
    map $http_upgrade $connection_upgrade {
        default upgrade;
        ''      close;
    }

    ...
    ...
    ...
  }

Post Migration

  • Eventuell konfigurierte Cronjobs können nun entfernt werden, weil der Docker Container einen eigenen Cronjob beinhaltet.
  • Zertifikate der Cloud können migriert werden, sind aber kein muss, falls die Nextcloud sich selbst über den Reverse-Proxy (mit offiziellem Zertifikat) erreichen
  • Indicies fixen
Zertifikate importieren
docker exec -ti -u 1000 -w /app/www/public nextcloud_app php occ security:certificates
docker exec -ti -u 1000 -w /app/www/public nextcloud_app php occ security:certificates:import /config/keys/cert.crt
docker exec -ti -u 1000 -w /app/www/public nextcloud_app php occ security:certificates
Indicies anlegen
docker exec -ti -u 1000 -w /app/www/public nextcloud_app php occ db:add-missing-indices
OCC Skript korrigieren

Solltest du ein occ Script als Wrapper für Nextcloud verwenden, können Anpassungen nötig sein, weil eine andere User-ID verwendet wird.
Einfach in die Datei /usr/local/sbin/occ packen:

#!/bin/bash
docker exec -ti -u 1000 -w /app/www/public nextcloud_app php occ $*

Danach die Berechtigung fixen

chmod 700 /usr/local/sbin/occ

Nextcloud PHP Einstellungen anpassen

Hinweis

In diesem Artikel werden Einstellungen für das Docker Image nextcloud:X-fpm, bzw. nextcloud:fpm beschrieben! Diese können ebenfalls beim Apache Image Anwendung finden, werden aber andere Pfade benötigen…

Allgemeines

Im Grundzustand sind alle nötigen Einstellungen des PHP-FPM Prozesses bereits vorgenommen. In den Meisten Fällen werden diese ausreichen, allerdings können je nach Bedarf Individualisierungen vorgenommen werden. Hierbei können Einstellungen wie zum Beispiel das Memory Limit (RAM Speicher), die Größe der Uploads, oder auch die Menge der zu erstellenden Prozesse angepasst werden. Gerade Letztes ist für größere Installationen mit vielen Benutzern relevant, denn wenn die Gesamtheit der Prozesse mit der Bearbeitung von Anfragen beschäftigt ist, können verlängerte Wartezeiten auftreten.

Konfigurationen

Alle Einstellungen befinden sich im Container unterhalb des folgenden Ordners:

root@4a850acce455:/var/www/html# ls -lha /usr/local/etc/php-fpm.d/
total 56K
drwxr-xr-x 1 root root 4.0K Jul 22 03:10 .
drwxr-xr-x 1 root root 4.0K Jul 22 03:10 ..
-rw-r--r-- 1 root root  357 Jul 22 03:10 docker.conf
-rw-r--r-- 1 root root  20K Jul 22 03:10 www.conf
-rw-r--r-- 1 root root  20K Jul 22 03:10 www.conf.default
-rw-r--r-- 1 root root   45 Jul 22 03:10 zz-docker.conf

Standardkonfiguration

Für nextcloud existiert ein Pool mit dem Namen www. Dies erkennt man in den Konfigurationsdateien durch die [www] Sektion:

# grep -v -e '^;' -e '^$' www.conf

[www]
user = www-data
group = www-data
listen = 127.0.0.1:9000
pm = dynamic
pm.max_children = 5
pm.start_servers = 2
pm.min_spare_servers = 1
pm.max_spare_servers = 3
# grep -v -e '^;' -e '^$' docker.conf

[global]
error_log = /proc/self/fd/2
log_limit = 8192
[www]
access.log = /proc/self/fd/2
clear_env = no
catch_workers_output = yes
decorate_workers_output = no
#grep -v -e '^;' -e '^$' zz-docker.conf

[global]
daemonize = no
[www]
listen = 9000

Im Standardfall bindet der PHP-FPM Prozess sich an Port 9000 und loggt seine Ausgaben an Stderr (/proc/self/fd/2), sodass die Ausgaben mittels docker logs eingesehen werden können.

Einstellungen überladen

Es bietet sich also an, entweder alle Dateien (den Ordner) via Volume, oder die zz-docker.conf zu überladen, falls lediglich einzelne Einstellungen notwendig sind. In diesem Beispiel wird der komplette php-fpm.d Ordner überladen (3. Zeile unter Volumes). Dadurch können wir alle Dateien individuell gestalten und vermischen diese nicht:

version: '3'

services:
  nextcloud:
    container_name: nextcloud
    image: nextcloud:21-fpm
    volumes:
      - /srv/nextcloud/run:/var/run/php/
      - /srv/nextcloud/html/:/var/www/html/
      - /srv/nextcloud/config/php-fpm.d:/usr/local/etc/php-fpm.d/
    restart: unless-stopped
    env_file: /srv/nextcloud/config/env_nextcloud

Nun müssen noch die Einstellungen innerhalb einer Datei vorgenommen werden. Die anderen müssen nicht angelegt werden!

Beispiel-Konfiguration

# File: /srv/nextcloud/config/php-fpm.d/zz-docker.conf

[www]
# Benutzer / Gruppe für Datei-Operationen
user = www-data
group = www-data
# Socket an dem der PHP-FPM Prozess lauscht (Webserver kommuniziert hier mit PHP)
listen = /var/run/php/nc.socket
# Benutzer / Gruppe dem der Socket "gehoert"
listen.owner = www-data
listen.group = www-data

# dynamisches erstellen von PHP-Prozessen (je nach Bedarf)
# Alternative: ondemand (hier werden nur Prozesse bei Bedarf gestartet und nach einer Gewissen Zeit komplett abgeräumt)
pm = dynamic
# maximal 8 Prozesse koennen gestartet werden
pm.max_children = 8
# ein Prozess von Beginn starten und bereithalten
pm.start_servers = 1
pm.min_spare_servers = 1
pm.max_spare_servers = 8

env[PATH] = /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
# Upload Groeße erhoeht auf 4 GB 
php_admin_value[post_max_size] = 4096M
php_admin_value[upload_max_filesize] = 4096M

[global]
# notwendig damit php-fpm im Vordergrund läuft und der Docker Container nicht abstürzt (siehe /entrypoint.sh)
daemonize = no

PHP-Einstellungen anpassen

Unabhängig von den hier genannten Einstellungen, können eine Vielzahl mitels php_admin_value und php_admin_flag gesetzt werden. Die offiziellen Flags / Werte findet man in der PHP-Dokumentation:

https://www.php.net/manual/de/install.fpm.configuration.php

Nginx-Anbindung mit Socket

Weil php-fpm nun nicht mehr an einem TCP-Port (9000) lauscht, sondern einen Unix-Socket bereitstellt (/var/run/php/nc.socket), müsste man diesen in der nginx Konfiguration hinterlegen:

Eine komplette Beschreibung der Nginx Konfiguration findet man in der offiziellen Dokumentation, welche den Socket-Betrieb bereits vorgesehen hat: https://docs.nextcloud.com/server/21/admin_manual/installation/nginx.html


Subsections of Unifi

LinuxServer.io Migration von unifi-controller zu unifi-network-application

Hinweis

Bevor etwas umgestellt wird, solltest du unbedingt ein Backup anlegen.
Dieses sollte über das Unifi Webinterface erstellt und heruntergeladen werden, weil es zum Ende benötigt wird!

Vorwort

Linuxserver.io hat den Support für das Image linuxserver/unifi-controller eingestellt, wie auf der offiziellen Webseite gezeigt wird: https://docs.linuxserver.io/images/docker-unifi-controller/

Deswegen sollten bestehende Installationen auf das neue Image migriert werden. Weil dieses aber keine MongoDB eingebaut hat, muss ein externer Datenbank-Server erstellt werden.

Die Anleitung besteht aus folgenden Schritten:

  1. Backup über das Webinterface erstellen
  2. MongoDB Container erstellen
  3. alten Unifi Container stoppen
  4. neuen Unifi Container erstellen
  5. Backup über Webinterace einspielen

Netzwerk

In dieser Anleitung wird ein eigenes Netzwerk für die MongoDB und den Unifi Controller erstellt.
Durch die festen IP-Adressen kann ein davorgestellter nginx und andere Dienste einfacher konfiguriert werden.

Container IP-Adresse
MongoDB 172.25.0.2
Unifi Controller 172.25.0.3

Die Ports der Anwendungen können entweder in der docker-compose.yml freigegeben werden, oder deine Firewall übernimmt dies, je nach persönlicher Präferenz.
Werden die Port-Freigaben von Docker nicht benötigt, kannst du diese in den docker-compose.yml Dateien einfach entfernen.

Backup erstellen & herunterladen

Hinweis

Das Erstellen des Backups kann je nach Datenmenge etwas dauern

  • Einloggen
  • Einstellung
  • Backup
  • “Backup / Wiederherstellen”
  • Backup herunterladen (& den gewünschten Zeitraum der migriert werden soll wählen)
  • Datei herunterladen

Konfiguration

Beim Unifi-Controller und Init Skript muss noch das Kennwort gesetzt werden, also das “xxxxx” ersetzen!

Docker-Compose
version: "3"
services:
  unifi-db:
    # aktuell ist offiziell Mongo <= 4.4 supported!
    image: docker.io/mongo:4.4
    container_name: unifi-db
    volumes:
      - /srv/docker/containers/unifi-db/db:/data/db
      - /srv/docker/containers/unifi-db/init-mongo.js:/docker-entrypoint-initdb.d/init-mongo.js:ro
    restart: unless-stopped
    networks:
      default:
        ipv4_address: 172.25.1.2
    ports:
      - 27017:27017

  unifi-controller:
    image: lscr.io/linuxserver/unifi-network-application:latest
    container_name: unifi-controller
    environment:
      PUID: 1000
      PGID: 1000
      MONGO_USER: "unifi"
      MONGO_PASS: "xxxxxx"
      MONGO_HOST: "172.25.1.2"
      MONGO_PORT: "27017"
      MONGO_DBNAME: "unifi"
      MEM_LIMIT: "1024"
      MEM_STARTUP: "1024"
    volumes:
      - /srv/docker/containers/unifi-controller/config/:/config
    restart: unless-stopped
    networks:
      default:
        ipv4_address: 172.25.1.3
    ports:
      - 8443:8443
      - 3478:3478/udp
      - 10001:10001/udp
      - 8080:8080
      - 1900:1900/udp #optional
      - 8843:8843 #optional
      - 8880:8880 #optional
      - 6789:6789 #optional
      - 5514:5514/udp #optional

networks:
  default:
    driver: bridge
    ipam:
      driver: default
      config:
        - subnet: 172.25.1.0/24
    driver_opts:
      com.docker.network.bridge.name: br_unifi
MongoDB Init Skript

Erst legen wir den Container Ordner an, damit im Anschluss das Init-Skript angelegt werden kann.
Das Skript legt beim ersten Start die Logindaten des Unifi Controllers an.

PW="xxxxx"

mkdir -p /srv/docker/containers/unifi-db/
cat - > /srv/docker/containers/unifi-db/init-mongo.js <<EOF
db.getSiblingDB("unifi").createUser({user: "unifi", pwd: "${PW}", roles: [{role: "dbOwner", db: "unifi"}]});
db.getSiblingDB("unifi_stat").createUser({user: "unifi", pwd: "${PW}", roles: [{role: "dbOwner", db: "unifi_stat"}]});
EOF
Docker stop / start

Nun muss der alte Unifi Controller gestoppt werden, damit die Ports wieder freigegeben werden.
Danach kann die neue Umgebung hochgefahren werden.

docker stop <unifi-controller>
docker-compose up -d

optional: Reverse Proxy

Sollte ein reverse Proxy wie (nginx, Traefik, …) vor dem Controller stehen, muss das Backend angepasst werden auf 172.25.0.3:8443.
Abweichungen sind allerdings je nach deiner Umgebung durchaus möglich…

Backup einspielen

Nach kurzer Zeit sollte das Webinterface wieder verfügbar sein. Entweder über den Port 8443, oder den Reverse Proxy mit gewohnter URL. Das Portal bietet das Hochladen und Einspielen des Backups an. Hier muss die *.unf Datei, die zu Beginn heruntergeladen wurde, gewählt werden.

Je nach Größe des Backups, kann dies mehrere Minuten dauern.
Die Webseite lädt automatisch neu, wenn die Wiederherstellung durch ist.

LinuxServer.IO - Unifi Controller Image
https://docs.linuxserver.io/images/docker-unifi-network-application/#docker-compose-recommended-click-here-for-more-info