16 KiB
mareike auf endeavour ausrollen (KeyHelp-Server mit Docker)
Diese Anleitung beschreibt, wie mareike zusätzlich zum bestehenden Server auf endeavour läuft. Endeavour ist ein
Server, der mit KeyHelp verwaltet wird. Die Parent-Domain ist meine-anmeldung.digital.
KeyHelp selbst nutzt kein Docker. mareike läuft deshalb in zwei Containern neben KeyHelp. KeyHelp übernimmt weiter, was es gut kann: Domains, Let's-Encrypt-Zertifikate, MariaDB, Postfächer und Backups.
Der bestehende Server bleibt unberührt. Für endeavour gibt es eigene Dateien unter
docker/endeavour/.docker-compose.prod,docker/prod.Dockerfile,docker/run-mareike.sh,docker/nginx/default.confund.dockerignorewerden nicht verändert und von endeavour nicht benutzt.
1. Überblick
Browser ──HTTPS──▶ KeyHelp-Webserver (Apache/nginx, Let's Encrypt, eine Domain je Tenant)
│ Proxy, Host-Header bleibt erhalten
▼
127.0.0.1:8080 ──▶ Container "web" (nginx, liefert public/ aus)
│ FastCGI
▼
Container "app" (PHP 8.5-FPM, mareike)
│ Unix-Socket /run/mysqld/mysqld.sock
▼
KeyHelp-MariaDB (Host)
- Tenant-Erkennung:
IdentifyTenantsucht den Tenant übertenants.url= Host der Anfrage. Deshalb muss der Proxy den Host-Header unverändert weitergeben, und jede Tenant-Domain braucht exakt diesen Eintrag intenants.url. - HTTPS: TLS endet bei KeyHelp. Die nginx im Container meldet PHP trotzdem
HTTPS=on, damit Laravelhttps://-Links erzeugt. Eine Code-Änderung ist dafür nicht nötig. - Port 8080 ist nur an
127.0.0.1gebunden und von außen nicht erreichbar.
Dateien im Repo
| Datei | Zweck |
|---|---|
docker/endeavour/Dockerfile |
Multi-Stage-Build. Target app enthält PHP-FPM, Code, vendor/ und Assets, Target web enthält nginx und public/. |
docker/endeavour/Dockerfile.dockerignore |
Eigener Build-Kontext. Unter anderem kommt keine .env ins Image. |
docker/endeavour/compose.yaml |
Stack mit den Services app und web. |
docker/endeavour/nginx.conf |
nginx im Container, inkl. HTTPS on und echter Client-IP. |
docker/endeavour/php.ini |
Upload-Größen, Speicher, OPcache. |
docker/endeavour/env.production.example |
Vorlage für die .env auf dem Server. |
docker/endeavour/deploy.sh |
Deploy-Skript: pullen, bauen, migrieren, starten. |
Verzeichnisse auf endeavour
/opt/mareike/
├── bin/deploy.sh Kopie von docker/endeavour/deploy.sh
├── src/ Git-Checkout (wird bei jedem Deploy aktualisiert)
├── shared/.env Konfiguration mit Zugangsdaten (nur root lesbar)
└── storage/ Laravel-storage/ (Logs, Sessions, erzeugte PDFs/ZIPs) – persistent
2. Docker installieren
Als root auf endeavour. Das Beispiel gilt für Debian, bei Ubuntu steht in den Pfaden ubuntu statt debian.
apt-get update
apt-get install -y ca-certificates curl
install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc
chmod a+r /etc/apt/keyrings/docker.asc
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] \
https://download.docker.com/linux/debian $(. /etc/os-release && echo "$VERSION_CODENAME") stable" \
> /etc/apt/sources.list.d/docker.list
apt-get update
apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
Log-Rotation für alle Container (/etc/docker/daemon.json):
{
"log-driver": "json-file",
"log-opts": { "max-size": "10m", "max-file": "5" }
}
Docker soll erst nach MariaDB starten, weil der Container das Socket-Verzeichnis /run/mysqld einbindet:
mkdir -p /etc/systemd/system/docker.service.d
cat > /etc/systemd/system/docker.service.d/after-mariadb.conf <<'EOF'
[Unit]
After=mariadb.service
Wants=mariadb.service
EOF
systemctl daemon-reload
systemctl restart docker
docker run --rm hello-world
KeyHelp-Firewall: Docker legt eigene iptables-Regeln an. Lädt KeyHelp seine Firewall neu, können diese Regeln verloren gehen. Die Container sind dann nicht mehr erreichbar (502 im Browser). Abhilfe ist
systemctl restart docker. Weil mareike nur an127.0.0.1lauscht, öffnet Docker nach außen keine Ports.
3. Systemnutzer, Verzeichnisse, Repository
Die Container laufen mit der UID eines eigenen Host-Nutzers, damit storage/ sauber beschreibbar ist. Der Name weicht
bewusst von den KeyHelp-Konten ab, weil KeyHelp für seine Konten selbst Systemnutzer anlegt.
getent passwd 2000 || useradd --system --uid 2000 --user-group --no-create-home --shell /usr/sbin/nologin mareike-docker
# Ist UID 2000 belegt, eine andere freie UID wählen. deploy.sh liest sie automatisch aus.
install -d -m 0755 /opt/mareike /opt/mareike/bin
install -d -m 0700 /opt/mareike/shared
install -d -m 0775 -o mareike-docker -g mareike-docker /opt/mareike/storage
Deploy-Key für das Repository anlegen (nur Lesezugriff):
ssh-keygen -t ed25519 -f /root/.ssh/mareike_deploy -N "" -C "endeavour-deploy"
cat /root/.ssh/mareike_deploy.pub # in Gitea beim Repo th.guenther/mareike als Deploy-Key (read-only) eintragen
cat >> /root/.ssh/config <<'EOF'
Host git.zahlenlabyrinth.de
Port 9922
User git
IdentityFile /root/.ssh/mareike_deploy
IdentitiesOnly yes
EOF
git clone ssh://git@git.zahlenlabyrinth.de:9922/th.guenther/mareike.git /opt/mareike/src
install -m 0750 /opt/mareike/src/docker/endeavour/deploy.sh /opt/mareike/bin/deploy.sh
Ändert sich
docker/endeavour/deploy.shim Repo, danach deninstall-Befehl erneut ausführen.
4. Datenbank in KeyHelp anlegen
-
Im KeyHelp-Panel ein Konto für mareike anlegen oder ein vorhandenes wählen, z. B.
anmeldung. Unter diesem Konto liegen später auch die Domains. -
Unter Datenbanken eine neue MariaDB-Datenbank mit Benutzer anlegen. Der Benutzer ist
@localhost. Den Remote-Zugriff nicht aktivieren, denn der Container verbindet sich über den Unix-Socket und gilt damit alslocalhost. -
Den Socket-Pfad prüfen:
mysql -e "SELECT @@socket;" # erwartet: /run/mysqld/mysqld.sockLiegt der Socket woanders, in
/opt/mareike/bin/deploy.shdie VariableMAREIKE_DB_SOCKET_DIRauf das Verzeichnis setzen undDB_SOCKETin der.envanpassen.
Weil die Datenbank von KeyHelp verwaltet wird, ist sie automatisch im KeyHelp-Backup enthalten.
5. .env anlegen
install -m 0600 /opt/mareike/src/docker/endeavour/env.production.example /opt/mareike/shared/.env
echo "base64:$(openssl rand -base64 32)" # Ergebnis als APP_KEY eintragen
nano /opt/mareike/shared/.env
Die wichtigsten Punkte:
APP_KEYwird einmal erzeugt und dann nie wieder geändert, weil Laravel damit verschlüsselte Daten sonst nicht mehr lesen kann.APP_URList die Domain des Dach-Tenantslv(siehe Abschnitt 6).DB_*sind die Daten aus Schritt 4.DB_HOST=localhostundDB_SOCKETbleiben wie in der Vorlage.MAIL_*: Am einfachsten ist ein Postfach in KeyHelp, etwanoreply@meine-anmeldung.digital. Als Host den Mail-Hostnamen des Servers eintragen, mit Port 587.WEBDAV_*: eigener Zielordner, damit sich endeavour und der bestehende Server nicht gegenseitig Dateien überschreiben.PROVIDERmuss leer bleiben. Mitbdp-lv-sachsenwürdedb:seeddie Sachsen-Tenants samt Personendaten einspielen.ASSET_URLnicht setzen, dann kommen die Assets von der jeweiligen Tenant-Domain.- In der Datei keine
${VAR}-Verweise verwenden, sondern alle Werte ausschreiben.
6. Erstinstallation
6.1 Erstes Deployment
/opt/mareike/bin/deploy.sh main # oder einen Release-Tag, z. B. 4.9.1
Das Skript holt den Code, baut beide Images, legt die Tabellen per migrate an und startet die Container. Der erste
Build dauert ein paar Minuten.
Kurztest:
/opt/mareike/bin/deploy.sh compose ps
curl -sI http://127.0.0.1:8080/ | head -1 # 404 ist hier richtig: es gibt noch keinen Tenant
6.2 Stammdaten einspielen
/opt/mareike/bin/deploy.sh artisan db:seed --force
Mit leerem PROVIDER läuft nur der ProductionDataSeeder. Er legt Rollen, Status, Zahlungsarten, Kostenstellentypen
und die Befreiungsgründe an. Den Befehl nur einmal ausführen, ein zweiter Lauf würde Duplikate anlegen.
6.3 Dach-Tenant lv und Cron-Aufgaben anlegen
Der Slug lv ist im Code fest als Dach-Ebene verdrahtet (LvOnlyMiddleware, Registrierung, Tenant-Verwaltung).
endeavour braucht deshalb genau einen Tenant mit diesem Slug. Seine url ist die Domain, die in Abschnitt 7 in KeyHelp
angelegt wird.
/opt/mareike/bin/deploy.sh artisan tinker
App\Models\Tenant::create([
'slug' => 'lv',
'name' => '<Name des Verbands>',
'url' => '<lv-subdomain>.meine-anmeldung.digital',
'email' => '<kontakt@…>',
'email_finance' => '<finanzen@…>',
'account_name' => '<Kontoinhaber>',
'account_iban' => '<IBAN>',
'account_bic' => '<BIC>',
'city' => '<Ort>',
'postcode' => '<PLZ>',
'is_active_local_group' => true,
'has_active_instance' => true,
]);
use App\Enumerations\CronTaskType;
App\Models\CronTask::create(['name' => 'UploadInvoices', 'execution_type' => CronTaskType::CRON_TASK_TYPE_REALTIME]);
App\Models\CronTask::create(['name' => 'CloseCostUnit', 'execution_type' => CronTaskType::CRON_TASK_TYPE_DAILY, 'schedule_time' => '00:05']);
App\Models\CronTask::create(['name' => 'CloseEvent', 'execution_type' => CronTaskType::CRON_TASK_TYPE_DAILY, 'schedule_time' => '00:10']);
App\Models\CronTask::create(['name' => 'NotifyTeam', 'execution_type' => CronTaskType::CRON_TASK_TYPE_DAILY, 'schedule_time' => '20:00']);
Die Cron-Aufgaben entsprechen denen des bestehenden Servers (BdpLvSachsenDataSeeder::installCronTasks()). Sie gelten
global und werden für jeden aktiven Tenant ausgeführt.
6.4 Erste Administratorin / ersten Administrator
-
Auf
https://<lv-subdomain>.meine-anmeldung.digitalganz normal registrieren. Voraussetzung: Abschnitt 7 ist erledigt und der Mailversand funktioniert. -
Das Konto zur Administration hochstufen:
mysql <datenbankname> -e "UPDATE users SET user_role_main='ROLE_ADMINISTRATOR', user_role_local_group='ROLE_GROUP_LEADER', active=1 WHERE email='<adresse>';"
7. Tenant-Domain in KeyHelp einrichten (je Tenant)
Diesen Ablauf für den Dach-Tenant lv und später für jede weitere Gruppe wiederholen.
7.1 Einmalig: Proxy-Module aktivieren (Apache)
a2enmod proxy proxy_http headers
systemctl reload apache2
7.2 Domain anlegen
-
KeyHelp → Domains → neue Domain für das mareike-Konto, z. B.
wilde-moehre.meine-anmeldung.digital. Den DNS-Eintrag (A/AAAA) auf endeavour setzen, falls KeyHelp den DNS nicht selbst verwaltet. -
SSL/TLS: Let's Encrypt aktivieren und HTTPS erzwingen einschalten.
-
PHP wird für diese Domain nicht gebraucht.
-
In den Domain-Einstellungen im Feld für individuelle Webserver-Direktiven (nur als Admin sichtbar, die Bezeichnung variiert je nach KeyHelp-Version) Folgendes eintragen:
Apache (KeyHelp-Standard):
ProxyPreserveHost On ProxyRequests Off RequestHeader set X-Forwarded-Proto "https" ProxyPass /.well-known/acme-challenge/ ! ProxyPass / http://127.0.0.1:8080/ ProxyPassReverse / http://127.0.0.1:8080/nginx (falls KeyHelp auf nginx läuft):
location / { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto https; client_max_body_size 70m; }Die Ausnahme für
/.well-known/acme-challenge/sorgt dafür, dass KeyHelp die Zertifikate weiter selbst verlängern kann. -
Speichern und die Domain aufrufen. Solange der Tenant noch nicht existiert, zeigt mareike 404 „Tenant not found“. Das ist ein gutes Zeichen, denn der Proxy funktioniert.
7.3 Tenant in mareike anlegen
Im Dach-Tenant lv unter der Tenant-Verwaltung den neuen Tenant anlegen. Als URL exakt die Domain ohne https://
und ohne Schrägstrich eintragen, z. B. wilde-moehre.meine-anmeldung.digital.
Für den Tenant lv selbst ist das bereits in Schritt 6.3 passiert.
8. Cron einrichten
mareike hat keinen Laravel-Scheduler. Die Aufgaben laufen über die URL /execute-crons, die für alle aktiven
Tenants arbeitet. Sie muss einmal pro Minute aufgerufen werden, dabei genügt der Host-Header eines beliebigen aktiven
Tenants:
cat > /etc/cron.d/mareike <<'EOF'
* * * * * root curl -fsS -o /dev/null -H "Host: <lv-subdomain>.meine-anmeldung.digital" http://127.0.0.1:8080/execute-crons
EOF
Kontrolle: /opt/mareike/storage/logs/ enthält die Task-Logs.
9. Updates ausrollen
/opt/mareike/bin/deploy.sh 4.9.2 # Release-Tag
/opt/mareike/bin/deploy.sh main # oder Branch
Das Skript läuft in dieser Reihenfolge:
git fetchund Checkout des Standes.storage/-Ordner prüfen.- Images neu bauen, dabei werden Basis-Images mit Sicherheitsupdates frisch gezogen.
migrate --forceausführen.- Container neu starten.
- Kompilierte Views leeren.
- Alte Images aufräumen.
Nützliche Kurzbefehle:
/opt/mareike/bin/deploy.sh compose ps
/opt/mareike/bin/deploy.sh compose logs -f web
/opt/mareike/bin/deploy.sh artisan migrate:status
/opt/mareike/bin/deploy.sh artisan tinker
Rollback: Den vorherigen Tag ausrollen (deploy.sh 4.9.1). Migrationen werden dabei nicht zurückgedreht.
Enthält ein Release Datenbankänderungen, deshalb vorher ein DB-Backup ziehen.
10. Backup
| Was | Wie |
|---|---|
| Datenbank | Über das KeyHelp-Backup, weil die DB von KeyHelp verwaltet wird. |
/opt/mareike/storage |
Nicht im KeyHelp-Backup. Zusätzlich sichern, z. B. per borg/restic oder rsync auf das Backup-Ziel. |
/opt/mareike/shared/.env |
Ebenfalls zusätzlich sichern, besonders wegen des APP_KEY. |
| Code und Images | Nicht nötig, lassen sich jederzeit aus dem Repo neu bauen. |
11. Fehlersuche
| Symptom | Ursache / Lösung |
|---|---|
| 404 „Tenant not found“ | Der Host passt nicht zu tenants.url (Tippfehler, https:// oder / im Eintrag), der Tenant ist nicht has_active_instance, oder der Proxy reicht den Host nicht durch (ProxyPreserveHost On fehlt). |
| 502/503 vom KeyHelp-Webserver | Container laufen nicht (deploy.sh compose ps) oder die Firewall wurde neu geladen, dann hilft systemctl restart docker. |
| Seite lädt ohne CSS, Mixed Content | ASSET_URL ist gesetzt oder die nginx-Config im Container wurde nicht übernommen (fastcgi_param HTTPS on). |
SQLSTATE[HY000] [2002] |
Socket nicht erreichbar. SELECT @@socket prüfen, MAREIKE_DB_SOCKET_DIR bzw. DB_SOCKET anpassen, nach einem MariaDB-Neustart den Stack mit deploy.sh compose restart app neu starten. |
Permission denied in storage/ |
Besitzer von /opt/mareike/storage muss mareike-docker sein, der nächste deploy.sh-Lauf korrigiert das. |
| Upload bricht ab | Grenzen prüfen: MAX_INVOICE_FILE_SIZE in der .env, 64 MB in php.ini, 70 MB in nginx.conf. |
| Mails kommen nicht an | MAIL_* prüfen, Log in storage/logs/. Absenderadresse muss zum Postfach passen (SPF/DKIM der Domain in KeyHelp). |
Logs:
/opt/mareike/bin/deploy.sh compose logs --tail=200 app web
ls -lt /opt/mareike/storage/logs/
12. Abgrenzung zum bestehenden Server
| bestehender Server | endeavour | |
|---|---|---|
| Dockerfile | docker/prod.Dockerfile |
docker/endeavour/Dockerfile |
| Compose | docker-compose.prod |
docker/endeavour/compose.yaml |
| nginx | docker/nginx/default.conf |
docker/endeavour/nginx.conf |
| Build-Kontext | .dockerignore |
docker/endeavour/Dockerfile.dockerignore |
| Datenbank | eigene | KeyHelp-MariaDB auf endeavour |
| Tenants | *.mareike.sachsen.pfadfinden.de |
*.meine-anmeldung.digital |
Beide Server nutzen denselben Code-Stand aus demselben Repository, haben aber getrennte Datenbanken, .env,
WebDAV-Ordner und Tenants. Änderungen an docker/endeavour/ wirken nur auf endeavour.