nextcloud docker haerten

Nextcloud als Docker produktiv machen – sicher, stabil und update-kontrolliert

Nextcloud als Docker produktiv machen – sicher, stabil und update-kontrolliert

Wie ich meine Nextcloud-Installation von „latest“-Überraschungen befreit, robuste Healthchecks eingebaut, Sicherheitswarnungen beseitigt und den Betrieb produktiv gemacht habe.


Ausgangslage

Nextcloud lief als Docker-Stack auf einer NAS und wurde über latest automatisch aktualisiert. Das führte zu ungewollten Major-Upgrades (z. B. auf Hub 25/NC 32) und kurzzeitigen Ausfällen von Apps. Ziel war ein stabiler, reproduzierbarer Produktivbetrieb.

Ziele

  • Keine Auto-Majorsprünge mehr – stattdessen feste Tags.
  • Robuste Healthchecks, damit der Stack erst „grün“ ist, wenn die Services wirklich bereit sind.
  • Watchtower/Auto-Updater entschärfen (Opt-out per Label).
  • Sicherheits- & Einrichtungswarnungen systematisch abarbeiten.

1) Images „pinnen“ (statt latest)

Der App-Container wurde auf den gewünschten Major-Zweig fixiert. Beispiel: nextcloud:32-apache (nur Minor-Updates innerhalb von 32). Für maximale Kontrolle kann man sogar eine exakte Version oder einen Digest pinnen.

# Variante: stabiler Major-Zweig
image: nextcloud:32-apache

# Variante: exakte Version
# image: nextcloud:32.0.0-apache

# Variante: Digest (bombenfest)
# image: nextcloud@sha256:... 

2) Watchtower-/Auto-Update Opt-out

Falls ein Auto-Updater aktiv ist, wird Nextcloud explizit ausgeschlossen:

labels:
  com.centurylinklabs.watchtower.enable: "false"

3) Healthchecks, die wirklich prüfen

MariaDB war „unhealthy“, obwohl sie lief – Ursache war ein zu strenger/alter Healthcheck. Der neue Check verwendet zuerst den Applikations-User, fällt bei Bedarf auf Root zurück und gibt der DB Zeit zum Aufwärmen:

healthcheck:
  test: ["CMD-SHELL",
         "mariadb -h 127.0.0.1 -u\"$MYSQL_USER\" -p\"$MYSQL_PASSWORD\" -e \"SELECT 1\" \
          || mariadb-admin ping -h 127.0.0.1 -uroot -p\"$MYSQL_ROOT_PASSWORD\" \
          || exit 1"]
  interval: 20s
  timeout: 5s
  retries: 15
  start_period: 90s

Bei Redis und Nextcloud prüfen wir echte Endpunkte (redis-cli ping, status.php), damit der Stack erst „healthy“ ist, wenn alle Abhängigkeiten stehen.

4) Trusted Proxy & HTTPS korrekt setzen

Um Drosselungen durch die Brute-Force-Erkennung zu vermeiden, tragen wir den Reverse-Proxy als Trusted Proxy ein und erzwingen das richtige Protokoll:

environment:
  TRUSTED_PROXIES: "10.0.0.21"          
  NEXTCLOUD_TRUSTED_DOMAINS: cloud.example.tld 10.0.0.5
  OVERWRITEHOST: cloud.example.tld
  OVERWRITEPROTOCOL: https

5) Sicherheits- & Einrichtungswarnungen aufräumen

  • Fehlende Indizes: occ db:add-missing-indices
  • MIME-Type-Migrationen: occ maintenance:repair --include-expensive
  • Standard-Telefonregion: occ config:system:set default_phone_region --value="DE"
  • Wartungsfenster: occ config:system:set maintenance_window_start --value="3" (03:00).
  • Integritätsprüfung:

    Core: occ integrity:check-core

    Apps (bei Bedarf): occ integrity:check-app <appname>, problematische App neu installieren oder EXTRA_FILE löschen.

6) Logging bändigen

Nach dem Aufräumen Log-Level auf „error“, dann rotieren:

occ log:manage --level error
occ log:manage --rotate

7) (Optional) Staging-Stack

Für künftige Major-Upgrades empfiehlt sich ein zweiter Stack mit separaten Volumes/DB und dem neuen Major-Tag. Erst nach erfolgreichem Test wird die Produktion angehoben.


Anonymisierte Compose-Basis

Die folgenden Snippets sind bewusst anonymisiert (keine realen IPs, Hostnamen, Passwörter):

MariaDB (mit robustem Healthcheck)

services:
  mariadb:
    image: mariadb:11.4-noble
    container_name: nc-db
    security_opt:
      - no-new-privileges:true
    command:
      - --transaction-isolation=READ-COMMITTED
      - --binlog-format=ROW
      - --innodb-read-only-compressed=OFF
    volumes:
      - /srv/docker/nextcloud/db:/var/lib/mysql
      - /srv/docker/nextcloud/mariadb-conf:/etc/mysql/conf.d
    environment:
      MYSQL_ROOT_PASSWORD: "REDACTED"
      MYSQL_PASSWORD: "REDACTED"
      MYSQL_DATABASE: nextcloud
      MYSQL_USER: nextcloud
      TZ: Europe/Berlin
    restart: unless-stopped
    healthcheck:
      test: ["CMD-SHELL",
             "mariadb -h 127.0.0.1 -u\"$MYSQL_USER\" -p\"$MYSQL_PASSWORD\" -e \"SELECT 1\" \
              || mariadb-admin ping -h 127.0.0.1 -uroot -p\"$MYSQL_ROOT_PASSWORD\" \
              || exit 1"]
      interval: 20s
      timeout: 5s
      retries: 15
      start_period: 90s
    labels:
      com.centurylinklabs.watchtower.enable: "false"

Redis

  redis:
    image: redis:7.2.5
    container_name: nc-redis
    volumes:
      - /srv/docker/nextcloud/redis:/data
    environment:
      TZ: Europe/Berlin
    restart: unless-stopped
    healthcheck:
      test: ["CMD-SHELL", "redis-cli ping || exit 1"]
      interval: 30s
      timeout: 10s
      retries: 5
    labels:
      com.centurylinklabs.watchtower.enable: "false"

Nextcloud App + Cron (NC 32-Zweig, Watchtower-Opt-out)

  nextcloud:
    image: nextcloud:32-apache
    container_name: nextcloud
    depends_on: [mariadb, redis]
    ports: ["8082:80"]
    environment:
      REDIS_HOST: redis
      NEXTCLOUD_ADMIN_USER: admin
      NEXTCLOUD_ADMIN_PASSWORD: "REDACTED"
      NEXTCLOUD_TRUSTED_DOMAINS: cloud.example.tld 10.0.0.5
      TRUSTED_PROXIES: "10.0.0.21"
      OVERWRITEHOST: cloud.example.tld
      OVERWRITEPROTOCOL: https
      MYSQL_PASSWORD: "REDACTED"
      MYSQL_DATABASE: nextcloud
      MYSQL_USER: nextcloud
      MYSQL_HOST: mariadb
      TZ: Europe/Berlin
      PHP_MEMORY_LIMIT: 1024M
      PHP_UPLOAD_LIMIT: 1024M
    volumes:
      - /srv/docker/nextcloud/html:/var/www/html
      - /srv/docker/nextcloud/custom_apps:/var/www/html/custom_apps
      - /srv/docker/nextcloud/config:/var/www/html/config
      - /srv/docker/nextcloud/data:/var/www/html/data
      - /srv/docker/nextcloud/themes:/var/www/html/themes
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "curl", "-fsS", "http://localhost/status.php"]
      interval: 30s
      timeout: 10s
      retries: 5
    labels:
      com.centurylinklabs.watchtower.enable: "false"

  cron:
    image: nextcloud:32-apache
    container_name: nextcloud-cron
    entrypoint: /cron.sh
    depends_on: [mariadb, redis]
    volumes:
      - /srv/docker/nextcloud/html:/var/www/html
      - /srv/docker/nextcloud/custom_apps:/var/www/html/custom_apps
      - /srv/docker/nextcloud/config:/var/www/html/config
      - /srv/docker/nextcloud/data:/var/www/html/data
    restart: unless-stopped
    labels:
      com.centurylinklabs.watchtower.enable: "false"

Lessons Learned

  1. Nie „latest“ in Produktion. Immer Tags/Digests pinnen.
  2. Healthchecks sind Gold wert. Sie verhindern „grüne“ Stacks mit halbfertigen Diensten.
  3. Sicherheitswarnungen sofort abarbeiten. Indizes & Migrations bringen messbare Performance und Ruhe in die Logs.
  4. Staging vor Produktion. Major-Upgrades zuerst testen, dann umstellen.

Mit diesen Schritten läuft Nextcloud bei mir jetzt stabil, reproduzierbar und ohne böse Überraschungen – genau so, wie eine Produktivumgebung sein soll.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert