Files
betreuungs-cockpit/docs/INSTALLATION.md
T
Florian Wilde 89bc5fe5a8 docs: Installationsanleitung Schritt fuer Schritt
Die README hatte Installationsabschnitte, aber keine durchgehende
Anleitung fuer jemanden, der vor einem frischen Server sitzt. Genau da
faengt diese hier an.

Dreizehn Schritte vom leeren Server bis zur laufenden Anwendung, jeder
mit einer Kontrolle am Ende - man geht nicht weiter, ohne geprueft zu
haben, ob der letzte Schritt geklappt hat. Am Ende eine Abnahmeliste
zum Abhaken, bevor echte Daten hineinkommen.

Aufgenommen sind die Fallstricke, die tatsaechlich Zeit kosten:

- AV-Vertrag klaeren, BEVOR der Server bestellt wird
- Zeitzone setzen, sonst rechnet der Server in UTC und verschiebt Fristen
- AUTH_URL muss exakt der aufgerufenen Adresse entsprechen - sonst landet
  die Anmeldung stumm wieder auf dem Formular, ohne Fehlermeldung. Das
  ist der Fehler, den man am laengsten sucht.
- Der Proxy muss X-Forwarded-For weiterreichen, sonst zaehlt die
  Login-Sperre alle Zugriffe als denselben Anschluss.
- Die beiden SQL-Skripte laufen nicht mit den Migrationen mit.

Zwei Kontrollen sind keine Formalie, sondern Nachweise: Der Schutz des
Audit-Logs wird geprueft, indem ein UPDATE ausdruecklich fehlschlagen
MUSS. Und die Sicherung wird nicht nur angestossen, sondern einmal
zurueckgespielt - solange noch nichts drinsteht, was weh tut.

Der letzte Schritt bringt ENC_KEY_V1 in Sicherheit, an zwei Orte, von
denen keiner der Server ist. Ein gestohlenes Backup ist ohne ihn
wertlos - und ein verlorener Schluessel macht auch das eigene Backup
wertlos. Das ist dieselbe Eigenschaft von zwei Seiten.

Etappe: 8
Phase: Haertung
2026-08-22 10:54:21 +02:00

22 KiB

Installation Schritt für Schritt

Von einem leeren Server bis zur laufenden Anwendung. Rechne mit 60 bis 90 Minuten, wenn du es zum ersten Mal machst.

Diese Anleitung setzt voraus, dass du dich per SSH auf einem Linux-Server anmelden und Befehle eintippen kannst. Mehr nicht — jeder Schritt steht ausgeschrieben da, und nach jedem Abschnitt steht eine Kontrolle, mit der du prüfst, ob es geklappt hat, bevor du weitermachst.

Wenn du die Anwendung nur ausprobieren willst, ohne Server: Der Abschnitt Schnellstart in der README bringt dich in zehn Minuten auf dem eigenen Rechner zum Laufen. Diese Anleitung hier ist für den echten Betrieb.


Inhalt


Schritt 0 — Was du vorher brauchst

Ein Server in Deutschland oder mindestens in der EU. Für eine Betreuung reicht die kleinste Größe bequem:

Minimum Bequem
Arbeitsspeicher 2 GB 4 GB
Speicherplatz 20 GB 40 GB
Kerne 2 2
Betriebssystem Debian 12 oder Ubuntu 24.04 dasselbe

Anbieter mit Rechenzentren in Deutschland: Hetzner (Falkenstein, Nürnberg), netcup (Nürnberg), IONOS (Karlsruhe). Ein Server für rund fünf Euro im Monat genügt.

Bevor du bestellst: Kläre den Auftragsverarbeitungsvertrag nach Art. 28 DSGVO. Bei Hetzner findest du ihn im Kundenkonto unter Verträge → AV-Vertrag, bei anderen ähnlich. Ohne ihn ist der Betrieb formal rechtswidrig — und es dauert fünf Minuten.

Eine Domain oder Subdomain, zum Beispiel cockpit.deine-domain.de. Setz einen A-Record auf die IP-Adresse des Servers, bevor du anfängst; DNS-Änderungen brauchen manchmal eine Stunde.

Etwas zum Aufschreiben. Du erzeugst gleich Schlüssel, die du nicht verlieren darfst. Ein Passwortmanager ist das Richtige.

Kontrolle

# Von deinem Rechner aus - zeigt die IP deines Servers?
dig +short cockpit.deine-domain.de

# Kommst du auf den Server?
ssh root@cockpit.deine-domain.de

Schritt 1 — Server vorbereiten

Alles Folgende läuft auf dem Server.

# System aktuell bringen
apt update && apt upgrade -y

# Werkzeuge, die wir brauchen
apt install -y git curl ca-certificates ufw

# Zeitzone setzen - sonst rechnet der Server in UTC und
# verschiebt Fristen um einen Tag
timedatectl set-timezone Europe/Berlin

Einen eigenen Benutzer anlegen. Als root zu arbeiten ist unnötig riskant:

adduser --gecos "" cockpit
usermod -aG sudo cockpit

# SSH-Schlüssel übernehmen, damit du dich weiter anmelden kannst
rsync --archive --chown=cockpit:cockpit ~/.ssh /home/cockpit/

Die Firewall einschalten. Nur SSH, HTTP und HTTPS von außen:

ufw allow OpenSSH
ufw allow 80/tcp
ufw allow 443/tcp
ufw --force enable

SSH absichern — Anmeldung nur mit Schlüssel, nicht mit Passwort. Nur machen, wenn du dich sicher mit Schlüssel anmelden kannst:

sed -i 's/^#*PasswordAuthentication.*/PasswordAuthentication no/' /etc/ssh/sshd_config
sed -i 's/^#*PermitRootLogin.*/PermitRootLogin prohibit-password/' /etc/ssh/sshd_config
systemctl restart ssh

Kontrolle

timedatectl | grep "Time zone"     # Europe/Berlin
ufw status                          # active, drei Regeln

Melde dich jetzt neu an, als cockpit:

ssh cockpit@cockpit.deine-domain.de

Schritt 2 — Docker installieren

# Offizielles Paketverzeichnis von Docker eintragen
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/debian/gpg \
  | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg

echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \
https://download.docker.com/linux/debian $(. /etc/os-release && echo "$VERSION_CODENAME") stable" \
  | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io \
  docker-buildx-plugin docker-compose-plugin

# Ohne sudo arbeiten dürfen
sudo usermod -aG docker $USER

Bei Ubuntu ersetzt du in den beiden URLs debian durch ubuntu.

Melde dich ab und wieder an, damit die Gruppenzugehörigkeit greift.

Kontrolle

docker --version              # Docker version 27.x oder neuer
docker compose version        # Docker Compose version v2.x
docker run --rm hello-world   # "Hello from Docker!"

Kommt bei docker run ein permission denied, hast du das Ab- und Anmelden vergessen.


Schritt 3 — Anwendung holen

sudo mkdir -p /opt/betreuungs-cockpit
sudo chown $USER:$USER /opt/betreuungs-cockpit

git clone https://git.wilde-server.de/florian.wilde/betreuungs-cockpit.git \
  /opt/betreuungs-cockpit

cd /opt/betreuungs-cockpit

# Auf die letzte veröffentlichte Fassung wechseln, nicht auf den
# laufenden Entwicklungsstand
git checkout v1.0.0

Kontrolle

ls docker-compose.yml Dockerfile .env.example   # alle drei da
git describe --tags                              # v1.0.0

Schritt 4 — Schlüssel erzeugen

Die Anwendung braucht vier Geheimnisse. Erzeuge sie auf dem Server, nicht auf dem eigenen Rechner:

docker run --rm node:22-alpine node -e "
const c = require('crypto');
const b = n => c.randomBytes(n).toString('base64');
console.log('AUTH_SECRET=' + b(32));
console.log('ENC_KEY_V1=' + b(32));
console.log('INDEX_KEY=' + b(32));
console.log('POSTGRES_PASSWORD=' + b(24).replace(/[+/=]/g,'x'));
console.log('SICHERUNG_PASSWORT=' + b(24).replace(/[+/=]/g,'x'));
console.log('S3_ACCESS_KEY_ID=' + b(12).replace(/[+/=]/g,'x'));
console.log('S3_SECRET_ACCESS_KEY=' + b(24).replace(/[+/=]/g,'x'));
"

Kopier die Ausgabe in einen Texteditor. Du brauchst sie gleich.

ENC_KEY_V1 ist der wichtigste Wert der ganzen Installation. Mit ihm sind Diagnosen, IBANs, Versicherungsnummern und alle Notizen verschlüsselt. Geht er verloren, sind diese Felder unwiederbringlich weg — auch aus jedem Backup. Es gibt keine Hintertür.

Schritt 13 sagt dir, wohin damit. Verlier ihn bis dahin nicht.


Schritt 5 — Konfiguration ausfüllen

cp .env.example .env
nano .env

Diese Werte musst du setzen:

# --- Datenbank ---
POSTGRES_PASSWORD=<aus Schritt 4>

# --- Anmeldung ---
AUTH_SECRET=<aus Schritt 4>
AUTH_URL=https://cockpit.deine-domain.de      # exakt deine Adresse, mit https
AUTH_TRUST_HOST=true

# --- Verschlüsselung ---
ENC_KEY_V1=<aus Schritt 4>
ENC_KEY_AKTIV=1
INDEX_KEY=<aus Schritt 4>

# --- Objektspeicher ---
S3_ACCESS_KEY_ID=<aus Schritt 4>
S3_SECRET_ACCESS_KEY=<aus Schritt 4>

# --- Sicherung ---
SICHERUNG_PASSWORT=<aus Schritt 4>

AUTH_URL muss exakt der Adresse entsprechen, unter der du die Anwendung später aufrufst — mit https://, ohne Schrägstrich am Ende. Steht dort etwas anderes, wird das Sitzungs-Cookie für die falsche Herkunft gesetzt: Die Anmeldung landet stumm wieder auf dem Formular, ohne Fehlermeldung. Das ist der Fehler, den man am längsten sucht.

Optional, aber empfohlen — Fristerinnerungen per E-Mail:

SMTP_HOST=mail.deine-domain.de
SMTP_PORT=587
SMTP_USER=cockpit@deine-domain.de
SMTP_PASSWORD=<dein Mailpasswort>
SMTP_FROM="Betreuungs-Cockpit <cockpit@deine-domain.de>"

Speichern mit Strg+O, Enter, Strg+X.

Die Datei absichern — sie enthält alle Schlüssel:

chmod 600 .env

Kontrolle

# Sind alle Pflichtwerte gefüllt? Zeigt nur die Namen, nicht die Werte.
grep -E '^(POSTGRES_PASSWORD|AUTH_SECRET|AUTH_URL|ENC_KEY_V1|INDEX_KEY|S3_ACCESS_KEY_ID|S3_SECRET_ACCESS_KEY|SICHERUNG_PASSWORT)=' .env \
  | sed 's/=.*/= gesetzt/'

Acht Zeilen müssen erscheinen. Fehlt eine, startet die Anwendung nicht — und sagt dir beim Start auch, welche.


Schritt 6 — Starten

cd /opt/betreuungs-cockpit

# Git-Stand mitgeben, damit später sichtbar ist, was läuft
GIT_COMMIT=$(git rev-parse --short HEAD) \
BUILD_ZEIT=$(date -Iseconds) \
docker compose up -d --build

Der erste Bau dauert fünf bis zehn Minuten — Tesseract und die deutschen Sprachdaten wollen heruntergeladen werden. Danach geht es schnell.

Kontrolle

docker compose ps

Vier Dienste, alle running, die Datenbank healthy:

NAME                            STATUS
betreuungs-cockpit-app-1        Up (healthy)
betreuungs-cockpit-db-1         Up (healthy)
betreuungs-cockpit-minio-1      Up (healthy)
betreuungs-cockpit-sicherung-1  Up
# Antwortet die Anwendung?
curl -s http://127.0.0.1:3000/api/gesundheit
# {"status":"bereit","version":"1.0.0","commit":"..."}

Kommt hier nichts, sieh in die Logs:

docker compose logs app --tail 50

Schritt 7 — Datenbank vorbereiten

Die Migrationen laufen beim Start automatisch mit. Zwei SQL-Skripte kommen einmalig von Hand dazu — sie richten die Volltextsuche ein und machen das Audit-Log unveränderlich:

docker compose exec app node node_modules/prisma/build/index.js db execute \
  --schema prisma/schema.prisma --file prisma/sql/volltextsuche.sql

docker compose exec app node node_modules/prisma/build/index.js db execute \
  --schema prisma/schema.prisma --file prisma/sql/audit-schutz.sql

Kontrolle

Prüf, dass der Schutz des Audit-Logs wirklich greift. Der folgende Befehl muss mit einem Fehler enden:

docker compose exec db psql -U cockpit betreuungs_cockpit \
  -c "UPDATE audit_log SET entitaet='x';"

Erwartete Ausgabe:

ERROR:  Das Audit-Log ist unveraenderlich. UPDATE auf audit_log ist nicht zulaessig.

Kommt stattdessen UPDATE 0, ist das Skript nicht gelaufen. Führ es erneut aus.


Schritt 8 — Dateiablage einrichten

Der Objektspeicher braucht einen Ablagebereich für die Dokumente:

source .env

docker compose exec minio mc alias set lokal \
  http://localhost:9000 "$S3_ACCESS_KEY_ID" "$S3_SECRET_ACCESS_KEY"

docker compose exec minio mc mb lokal/betreuungs-cockpit

Kontrolle

docker compose exec minio mc ls lokal
# [...] betreuungs-cockpit/

Wenn du statt MinIO einen externen Objektspeicher in der EU benutzt (z. B. Hetzner Object Storage), lass den minio-Dienst in der docker-compose.yml weg und zeig mit S3_ENDPOINT dorthin. Auch dafür brauchst du einen AV-Vertrag.


Schritt 9 — Reverse Proxy und TLS

Die Anwendung horcht bewusst nur auf 127.0.0.1:3000 und bringt keinen eigenen Proxy mit. Von außen erreichbar wird sie über den Proxy auf dem Server, der auch das Zertifikat besorgt.

Wenn noch kein Proxy da ist: Caddy

Caddy holt Let's-Encrypt-Zertifikate von selbst und erneuert sie:

sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' \
  | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' \
  | sudo tee /etc/apt/sources.list.d/caddy-stable.list
sudo apt update && sudo apt install -y caddy

Dann /etc/caddy/Caddyfile ersetzen:

sudo tee /etc/caddy/Caddyfile > /dev/null <<'ENDE'
cockpit.deine-domain.de {
    encode zstd gzip
    reverse_proxy 127.0.0.1:3000

    # Fotos von Behördenschreiben können groß sein
    request_body {
        max_size 30MB
    }
}
ENDE

sudo systemctl reload caddy

Wenn schon nginx läuft

server {
    listen 443 ssl http2;
    server_name cockpit.deine-domain.de;

    ssl_certificate     /etc/letsencrypt/live/cockpit.deine-domain.de/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/cockpit.deine-domain.de/privkey.pem;

    client_max_body_size 30M;

    location / {
        proxy_pass         http://127.0.0.1:3000;
        proxy_http_version 1.1;
        proxy_set_header   Host              $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   Upgrade           $http_upgrade;
        proxy_set_header   Connection        "upgrade";
    }
}

Das Zertifikat holst du mit sudo certbot --nginx -d cockpit.deine-domain.de.

Der Proxy muss X-Forwarded-For weiterreichen. Die Anwendung braucht die echte Adresse für die Login-Sperre und für das Audit-Log (dort nur als Hash). Ohne den Kopf zählt sie alle Zugriffe als denselben Anschluss.

Kontrolle

Von deinem eigenen Rechner aus:

curl -sI https://cockpit.deine-domain.de/anmelden | head -5

Erwartet: HTTP/2 200, dazu die Kopfzeilen content-security-policy, x-frame-options: DENY und strict-transport-security.


Schritt 10 — Ersteinrichtung im Browser

Ruf https://cockpit.deine-domain.de auf. Ein Assistent führt dich durch drei Schritte: Zugang anlegen, zweiten Faktor einrichten, betreute Person erfassen.

Die Einzelheiten stehen in der Bedienungsanleitung, Abschnitt Der erste Tag. Drei Dinge sind hier wichtig:

  1. Die Seite /ersteinrichtung sperrt sich selbst, sobald ein Zugang existiert. Niemand kann sich danach einen zweiten Administrator anlegen.
  2. Die Wiederherstellungscodes werden genau einmal angezeigt. Druck sie aus, bevor du weiterklickst.
  3. Das Bestellungsdatum muss stimmen — daraus entstehen alle gesetzlichen Fristen.

Zum Schluss: Fristen → Pflichtfristen ableiten.

Kontrolle

  • Du kommst mit E-Mail, Passwort und dem Code aus der App herein.
  • Unter Fristen stehen Einträge mit Paragraphenbezug.
  • Unter Protokoll siehst du deine eigene Anmeldung.
  • Unten links in der Seitenleiste steht Version 1.0.0 · <commit>.

Schritt 11 — Sicherung prüfen

Die tägliche Sicherung läuft ab jetzt um 03:15 Uhr. Warte nicht bis morgen — stoß sie einmal von Hand an:

cd /opt/betreuungs-cockpit
docker compose exec sicherung /bin/sh /sicherung.sh

Erwartete Ausgabe, in dieser Reihenfolge:

Sicherung beginnt: /backups/cockpit_2026-08-22_1430.dump.enc
Sicherung fertig (1.2M)
Selbstpruefung bestanden.

Die Selbstprüfung ist der eigentliche Punkt: Das Skript entschlüsselt die eben geschriebene Datei sofort wieder. Schlägt das fehl, endet der Lauf mit einem Fehler statt mit einem stillen Scheinerfolg.

Und jetzt einmal zurückspielen

Ein Backup, das nie zurückgespielt wurde, ist kein Backup. Probier es gleich aus, solange nichts drinsteht, was weh tut:

# Testdatenbank neben der echten anlegen
docker compose exec db createdb -U cockpit probe

# Zurückspielen
docker compose exec -e PGDATABASE=probe -e SICHERUNG_OHNE_RUECKFRAGE=ja \
  sicherung /bin/sh /wiederherstellung.sh /backups/cockpit_<datum>.dump.enc

# Stichprobe
docker compose exec db psql -U cockpit probe \
  -c "SELECT vorname, nachname FROM betreute;"

# Aufräumen
docker compose exec db dropdb -U cockpit probe

Kontrolle

ls -lh backups/
# cockpit_....dump.enc  und  cockpit_....dump.enc.sha256

Was das Backup nicht enthält: den Schlüssel ENC_KEY_V1 und die Dateien im Objektspeicher. Für die Dateien:

docker compose exec minio mc mirror lokal/betreuungs-cockpit /backups/dokumente

Und kopier die Sicherungen regelmäßig weg vom Server — ein Backup, das neben dem Original liegt, hilft bei einem Serverausfall nicht.


Schritt 12 — Wartungslauf einrichten

Einmal täglich: Fristerinnerungen verschicken, Papierkorb leeren, alte Anmeldeversuche entfernen.

# Schlüssel für den Endpunkt ausrechnen
source .env
docker compose exec app node -e "
console.log(require('crypto').createHmac('sha256', process.env.AUTH_SECRET)
  .update('wartung').digest('base64url'))
"

Den ausgegebenen Wert in einen cron-Eintrag:

crontab -e

Diese Zeile ans Ende:

30 4 * * * curl -fsS -H "X-Wartung-Schluessel: <ausgegebener Wert>" https://cockpit.deine-domain.de/api/wartung > /dev/null

Kontrolle

curl -s -H "X-Wartung-Schluessel: <wert>" \
  https://cockpit.deine-domain.de/api/wartung
# {"erinnerungen":0,"papierkorbGeleert":0,...,"fehler":[]}

Ohne gültigen Schlüssel antwortet die Route mit 404, nicht mit 401 — wer die Adresse errät, soll nicht einmal erfahren, dass es sie gibt.


Schritt 13 — Schlüssel in Sicherheit bringen

Der letzte Schritt, und der wichtigste.

ENC_KEY_V1 gehört an zwei Orte, keiner davon ist dieser Server:

  1. In deinen Passwortmanager.
  2. Ausgedruckt zu deinen Betreuungsunterlagen, im Ordner beim Betreuerausweis.

Dasselbe gilt für SICHERUNG_PASSWORT — ohne das nützt dir die verschlüsselte Sicherungsdatei nichts.

# Zum Abschreiben oder Kopieren
grep -E '^(ENC_KEY_V1|SICHERUNG_PASSWORT)=' .env

Warum so umständlich? Weil der Schlüssel bewusst nicht in der Datenbank liegt. Ein gestohlenes Backup ist damit wertlos — und ein verlorener Schlüssel macht auch dein eigenes Backup wertlos. Beides ist dieselbe Eigenschaft, von zwei Seiten betrachtet.

Denk an die letzten Pflichten:


Aktualisieren

cd /opt/betreuungs-cockpit

# 1. IMMER zuerst sichern - eine Migration lässt sich nicht zurückrollen
docker compose exec sicherung /bin/sh /sicherung.sh

# 2. Was ändert sich? Lies den Changelog, bevor du ziehst
git fetch --tags
git log --oneline HEAD..origin/main

# 3. Auf die neue Fassung wechseln
git checkout v1.1.0

# 4. Neu bauen und starten
GIT_COMMIT=$(git rev-parse --short HEAD) \
BUILD_ZEIT=$(date -Iseconds) \
docker compose up -d --build

Die Migrationen laufen beim Start mit. Sagt der Changelog, dass sich ein SQL-Skript geändert hat, führ die beiden Befehle aus Schritt 7 erneut aus — sie sind so gebaut, dass mehrfaches Ausführen nichts kaputtmacht.

Kontrolle

curl -s https://cockpit.deine-domain.de/api/gesundheit
# version muss die neue sein

Steht dort noch die alte Version, läuft der alte Behälter weiter — dann hat der Bau nicht durchgezogen. Sieh in docker compose logs app.


Wenn etwas nicht klappt

Ein Dienst startet nicht

docker compose ps
docker compose logs app --tail 100
docker compose logs db --tail 50

Fehlt eine Umgebungsvariable, sagt die Anwendung beim Start, welche.

„Umgebungsvariable ENC_KEY_V1 fehlt"

Die .env liegt nicht neben der docker-compose.yml, oder die Zeile ist leer. Prüf mit dem Befehl aus Schritt 5.

Die Anmeldung landet wieder auf dem Formular, ohne Fehlermeldung

AUTH_URL stimmt nicht mit der aufgerufenen Adresse überein. Häufig: http statt https, oder ein Schrägstrich am Ende. Nach dem Ändern:

docker compose up -d

„Zu viele Fehlversuche"

Nach fünf Fehlversuchen wird für eine Viertelstunde gesperrt — auch dich. Warte ab, oder setz die Zähler zurück:

docker compose exec db psql -U cockpit betreuungs_cockpit \
  -c "DELETE FROM login_versuch WHERE erfolg = false;"

Der Code aus der App wird nicht angenommen

Die Uhr des Telefons geht falsch. TOTP-Codes hängen an der Uhrzeit. Stell die automatische Zeitsynchronisierung ein und prüf, dass auch der Server richtig geht:

timedatectl

Die Texterkennung findet nichts

docker compose exec app tesseract --list-langs
# deu muss dabei sein

Fehlt sie, ist das Abbild nicht vollständig gebaut: docker compose build --no-cache app.

Uploads schlagen bei großen Dateien fehl

Der Proxy schneidet ab. Bei Caddy request_body max_size, bei nginx client_max_body_size — beide auf mindestens 30 MB.

Die Datenbank ist voll

df -h
docker system prune -a --volumes    # ACHTUNG: entfernt ungenutzte Volumes

Vorher prüfen, was da ist. docker system prune ohne --volumes ist der sichere Anfang.

Ganz von vorn

docker compose down -v      # ACHTUNG: löscht ALLE Daten
docker compose up -d --build

Das -v entfernt die Datenbank. Nur, wenn du das wirklich willst.


Abnahmeliste

Hak das durch, bevor du echte Daten einträgst:

  • AV-Vertrag mit dem Hoster abgeschlossen
  • AV-Vertrag für den Objektspeicher abgeschlossen (falls extern)
  • Server steht in Deutschland oder mindestens in der EU
  • Zeitzone auf Europe/Berlin
  • Firewall aktiv, nur 22, 80 und 443 offen
  • SSH-Anmeldung nur mit Schlüssel
  • https:// funktioniert, Zertifikat gültig
  • .env hat Rechte 600
  • Alle vier Dienste laufen, Datenbank healthy
  • /api/gesundheit antwortet mit der erwarteten Version
  • Audit-Schutz greift — UPDATE audit_log endet mit Fehler
  • Bucket im Objektspeicher angelegt
  • Zweiter Faktor eingerichtet
  • Wiederherstellungscodes ausgedruckt und weggelegt
  • Bestellungsdatum eingetragen, Pflichtfristen abgeleitet
  • Sicherung von Hand angestoßen, Selbstprüfung bestanden
  • Sicherung einmal zurückgespielt und geprüft
  • Sicherungen werden vom Server weg kopiert
  • Wartungslauf im cron eingetragen und einmal getestet
  • ENC_KEY_V1 im Passwortmanager und ausgedruckt
  • SICHERUNG_PASSWORT ebenso
  • Verarbeitungsverzeichnis ausgefüllt
  • Datenschutzhinweise ausgefüllt und ausgehändigt

Ist alles abgehakt, kannst du anfangen. Die Bedienungsanleitung sagt dir, wie.