- PHP 85.5%
- JavaScript 6.2%
- CSS 3.3%
- Mustache 2.6%
- Gherkin 0.9%
- Other 1.4%
| .forgejo/workflows | ||
| .github/workflows | ||
| config | ||
| custom-plugins | ||
| docker | ||
| docs | ||
| plugin-screenshots | ||
| scripts | ||
| site | ||
| .env.example | ||
| .env.local.example | ||
| .gitignore | ||
| docker-compose.local.yml | ||
| docker-compose.yml | ||
| FEATURE-KONZEPTE.md | ||
| HANDOVER-kursgenerator.md | ||
| HANDOVER-lernpfad.md | ||
| README.md | ||
| ROADMAP.md | ||
Moodle 5.2 auf Hetzner – mit lokalem Deploy-Flow
Dieses Repo richtet ein produktives Moodle 5.2 auf einem Hetzner-Server ein
(Docker Compose). Der aktuelle Arbeitsmodus ist lokal-first: Push auf main
führt CI aus, Production-Deploy läuft nur manuell über GitHub Actions
workflow_dispatch.
Architektur
Run workflow
│
▼
┌──────────────────┐ SSH ┌──────────────────────────────────────┐
│ GitHub Actions │ ─────────────► │ Hetzner-Server (/opt/moodle) │
│ (deploy.yml) │ │ │
└──────────────────┘ │ scripts/deploy.sh │
│ git pull → docker build │
│ → upgrade.php → purge_caches.php │
│ │
│ ┌────────┐ ┌────────┐ ┌────────┐ │
│ │ caddy │─►│ moodle │─►│ db │ │
│ │ TLS │ │ PHP8.4 │ │MariaDB │ │
│ └────────┘ │ Apache │ └────────┘ │
│ └───┬────┘ ┌────────┐ │
│ ┌───┴────┐ │ redis │ │
│ │ cron │ └────────┘ │
│ └────────┘ │
└──────────────────────────────────────┘
Was liegt im Repo (Empfehlung): nicht der Moodle-Core, sondern nur deine
eigenen Plugins/Themes plus die Infrastruktur. Der Core wird beim Build an einem
festgepinnten Tag (MOODLE_GIT_REF, z. B. v5.2.1) frisch von GitHub
geklont. Vorteile: kleines Repo, keine Merge-Konflikte mit dem Core, und ein
Core-Update ist nur eine Zeile in der .env. Deine Plugins liegen unter
custom-plugins/ (siehe custom-plugins/README.md).
Verzeichnisstruktur
moodle-hetzner/
├── docker-compose.yml # caddy + moodle + cron + db + redis
├── .env.example # Vorlage für Secrets/Config (→ .env auf Server)
├── config/config.php # Moodle-Config, komplett über ENV gesteuert
├── custom-plugins/ # deine Plugins/Themes (→ public/<type>/)
├── docker/
│ ├── Dockerfile # PHP 8.4 + Apache + Moodle-Core (gepinnt)
│ ├── php.ini # max_input_vars=5000, OPcache, Limits
│ ├── apache-moodle.conf # DocumentRoot → public/
│ ├── Caddyfile # Auto-HTTPS via Let's Encrypt
│ └── entrypoint.sh # wartet auf DB, setzt Rechte
├── scripts/
│ ├── server-setup.sh # einmaliger Server-Bootstrap
│ ├── install.sh # einmalige Moodle-Erstinstallation
│ └── deploy.sh # läuft bei jedem Deploy (von CI getriggert)
└── .github/workflows/deploy.yml
Eckdaten Moodle 5.2 (verifiziert)
| Komponente | Anforderung |
|---|---|
| Release | 5.2.0 am 20.04.2026, aktueller Punkt-Release 5.2.1 |
| PHP | min. 8.3, 8.4 unterstützt, 64-bit, Extension sodium Pflicht |
max_input_vars |
≥ 5000 |
| Datenbank | MariaDB ≥ 10.11 · PostgreSQL ≥ 16 · MySQL ≥ 8.4 |
| Web-Root | das public/-Verzeichnis (Code-Restructure seit 5.1) |
config.php |
liegt im Install-Root, CLI-Skripte unter admin/cli/ im Root |
Dieses Setup nutzt PHP 8.4, MariaDB 11.4 und Redis.
Schritt 1 – Hetzner-Server bestellen
- Bei Hetzner Cloud ein Projekt anlegen und
einen Server erstellen.
- Image: Ubuntu 26.04 LTS (Fallback 24.04 LTS, falls 26.04 noch nicht im Menü ist)
- Typ: Demo/leicht CX23 (2 vCPU, 4 GB, 40 GB SSD, ~5,49 €/Mon); empfohlen für echten Betrieb CX33 (4 vCPU, 8 GB, 80 GB), da Moodle + MariaDB + Redis + Caddy + Cron parallel laufen. Reihe: CX23 → CX33 → CX43 → CX53.
- Netzwerk: IPv4 aktiviert lassen (für Let's Encrypt + breite Erreichbarkeit dringend empfohlen – siehe Hinweis unten).
- SSH-Key: deinen öffentlichen Key hinterlegen (sonst Passwort per Mail).
- Server-IP notieren.
Schritt 2 – DNS setzen
Beim Domain-Provider einen A-Record anlegen, der demo.eledia.ai auf die
IPv4 des Servers zeigen lässt, z. B. demo.eledia.ai → 203.0.113.10.
Hast du auch eine IPv6, zusätzlich einen AAAA-Record setzen.
Wichtig: Caddy holt das TLS-Zertifikat erst, wenn der DNS-Eintrag aktiv ist.
Schritt 3 – Repo auf GitHub anlegen
- Auf GitHub ein privates Repo erstellen (z. B.
moodle-hetzner). - Diesen Ordner hochladen:
cd moodle-hetzner git init && git add . && git commit -m "Initial Moodle 5.2 stack" git branch -M main git remote add origin git@github.com:DEINUSER/moodle-hetzner.git git push -u origin main
Schritt 4 – Server bootstrappen
Per SSH als root auf den Server, dann:
curl -fsSL https://raw.githubusercontent.com/DEINUSER/moodle-hetzner/main/scripts/server-setup.sh -o server-setup.sh
bash server-setup.sh git@github.com:DEINUSER/moodle-hetzner.git
Das installiert Docker, legt den User deploy an, konfiguriert die Firewall und
klont das Repo nach /opt/moodle.
Damit GitHub Actions und der Server auf das private Repo zugreifen können, hinterlege einen Deploy-Key: erzeuge auf dem Server
sudo -u deploy ssh-keygen -t ed25519und trage den public Key in den GitHub-Repo-Einstellungen unter Deploy keys (read-only genügt) ein.
Schritt 5 – .env ausfüllen & installieren
sudo -u deploy -i
cd /opt/moodle
cp .env.example .env
nano .env # Domain, E-Mail, DB-Passwörter eintragen
ADMIN_PASS='EinStarkesPasswort!' ./scripts/install.sh
Danach ist Moodle unter deiner Domain erreichbar (Caddy holt automatisch ein
Let's-Encrypt-Zertifikat). Login mit admin / deinem ADMIN_PASS.
Schritt 6 – Manuellen Production-Deploy aktivieren (GitHub Secrets)
Im GitHub-Repo unter Settings → Secrets and variables → Actions anlegen:
| Secret | Wert |
|---|---|
SSH_HOST |
Server-IP oder Hostname |
SSH_USER |
deploy |
SSH_PORT |
22 |
SSH_KEY |
privater SSH-Key, mit dem sich GitHub als deploy einloggt |
Den passenden Public-Key in ~deploy/.ssh/authorized_keys auf dem Server
eintragen. Ab jetzt kann der Production-Deploy in GitHub Actions manuell über
CI & Deploy Moodle → Run workflow gestartet werden:
Push auf
maindeployt nicht automatisch. Er läuft durch die CI; der Server-Deploy bleibt ein bewusster manueller Schritt.
Lokale Moodle-Instanz mit OrbStack
Für lokale Tests gibt es einen getrennten Compose-Stack ohne Caddy und ohne
HTTPS-Zwang. Er nutzt dieselbe Moodle-Version, dieselben Custom-Plugins und
dieselben Upgrade-/Translation-Wartungsschritte wie der Server-Deploy, läuft
aber nur lokal auf http://localhost:8080.
cp .env.local.example .env.local
./scripts/local-deploy.sh
Danach:
./scripts/local-deploy.sh logs # Moodle-Logs
./scripts/local-deploy.sh down # Container stoppen
./scripts/local-deploy.sh up # Container wieder starten
./scripts/local-deploy.sh reset # lokale Volumes löschen und neu installieren
PHPUnit für lokale Plugin-Tests läuft im selben lokalen Stack mit separatem
Tabellenpräfix (phpu_) und eigenem Dataroot. Nach dem ersten Deploy oder nach
einem Reset initialisieren und dann die Testsuite ausführen:
./scripts/local-deploy.sh phpunit-init
./scripts/local-deploy.sh phpunit
PHPUNIT_TESTSUITE=local_ragingest_testsuite ./scripts/local-deploy.sh phpunit
Login-Defaults aus .env.local.example:
admin / Admin123!
Wenn Port 8080 belegt ist, in .env.local MOODLE_LOCAL_PORT und
MOODLE_WWWROOT gemeinsam ändern, zum Beispiel auf 8081 und
http://localhost:8081.
Dieser lokale Flow ist bewusst vom Produktions-Deploy getrennt. Ein Commit/Push
auf GitHub kann weiterhin gemacht werden, aber der lokale Test läuft über
docker-compose.local.yml und löst keinen Server-Deploy aus.
Laufender Betrieb
Moodle-Core aktualisieren
In .env MOODLE_GIT_REF auf den neuen Tag setzen (z. B. v5.2.2),
committen, pushen. Die Pipeline baut das Image neu und führt upgrade.php aus.
Tipp: Da
.envnicht im Repo liegt, wirdMOODLE_GIT_REFauf dem Server gepflegt. Alternativ den Default-Tag imdocker/Dockerfile(ARG) bzw. in der Server-.envanheben. Für ein rein git-getriebenes Core-Update den Tag in einer versionierten Datei führen – siehe Hinweis unten.
Plugin/Theme aktualisieren
Code unter custom-plugins/<type>/<name>/ ändern, version.php hochzählen,
committen, pushen → CI läuft. Für Production anschließend den Workflow manuell
starten.
Manuelles Deploy / Logs
cd /opt/moodle
./scripts/deploy.sh # manuell deployen
docker compose logs -f moodle # Logs ansehen
docker compose ps # Status
Backup
# Datenbank
docker compose exec -T db mariadb-dump -u root -p"$MOODLE_DB_ROOT_PASS" "$MOODLE_DB_NAME" > backup.sql
# moodledata-Volume
docker run --rm -v moodle-hetzner_moodledata:/data -v "$PWD":/backup alpine \
tar czf /backup/moodledata.tar.gz -C /data .
Für Produktion: dieses Backup per Cron + Offsite-Storage (z. B. Hetzner Storage Box) automatisieren.
Hinweise & Designentscheidungen
- Kein Core im Repo: sauberer, kleineres Repo, einfaches Core-Update. Wenn du Core-Patches brauchst, kannst du stattdessen einen eigenen Moodle-Fork pinnen.
- Upgrade nur im Deploy, nicht beim Container-Start: ein Neustart löst nie versehentlich eine Schema-Migration aus. Während des Upgrades ist der Wartungsmodus aktiv.
config.phpüber ENV: keine Secrets im Image, alles kommt aus.env.- Sicherheit:
.envniemals committen (steht in.gitignore); Deploy-Key read-only; Firewall offen nur für 22/80/443;moodledataaußerhalb des Web-Roots.