All-in-one Bludit: Hosting mit Docker und Caddy/FrankenPHP

Bludit via Docker und Caddy/FrankenPHP mit Domains und SSL/TLS-Zertifikaten in einer Compose — simples Setup für ein Flat-File-CMS ohne Datenbank oder zusätzliche Container.

Docker als Basis

Bludit lässt sich hervorragend via Docker hosten und benötigt weder eine Datenbank noch zusätzliche Container. Alles kann in eine Compose gepackt werden. Caddy dient in Kombination mit FrankenPHP als PHP-Webserver für Bludit und stellt SSL/TLS-Zertifikate automatisch via Let's Encrypt bereit. Das Caddyfile zur Domain-Verwaltung lässt sich direkt in die Compose integrieren. Es werden weder ein Dockerfile noch eine .env benötigt. Weitere Projekte lassen sich jederzeit in neuen Verzeichnissen anlegen.

Im Folgenden wird ein komplettes Setup beschrieben, das so auch diesen Blog ausliefert.

[!NOTE] Ersetze in allen Beispielen example.com durch deine eigene Domain. Die Domain muss per A-/AAAA-Record auf den genutzten Server zeigen, sonst schlägt die Zertifikatsausstellung fehl.

Warum Caddy/FrankenPHP?

FrankenPHP stellt die PHP-Funktionalität für Caddy als Webserver bereit. Die kombinierte Nutzung von Caddy und FrankenPHP ermöglicht ein simples Setup für Bludit mit Erweiterungsmöglichkeiten. Das heißt konkret:

  • Ein Container statt drei. Kein nginx + php-fpm + certbot-Gebastel, kein separates Docker-Netzwerk zwischen Webserver und PHP.
  • HTTPS ohne Konfiguration. Automatische Bereitstellung sowie Erneuerung von Zertifikaten, sobald eine Domain im Caddyfile steht.
  • Ein vollwertiges Caddyfile. Redirects, weitere vHosts oder Reverse-Proxies für andere Dienste — alles im selben Container.
  • HTTP/3 out of the box, wenn man UDP 443 mit veröffentlicht.

Und Bludit passt perfekt dazu: Als Flat-File-CMS speichert es Inhalte als JSON- und Textdateien direkt auf der Festplatte. Es gibt also keinen Datenbank-Container, kein Volume-Chaos und keine anstrengenden Migrationen.

Voraussetzungen

  • Ein Server mit Docker Engine und Compose-Plugin v2 (die Inline-Features dockerfile_inline und configs.content brauchen ein halbwegs aktuelles Compose v2)
  • Die Ports 80 (TCP) und 443 (TCP und UDP) frei und aus dem Internet erreichbar
  • Ein DNS-Record, der auf den Server zeigt

Die Compose-Datei

Lege ein Projektverzeichnis an und speichere darin folgende compose.yml:

services:
  php:
    build:
      context: .
      dockerfile_inline: |
        FROM dunglas/frankenphp
        RUN install-php-extensions gd
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
      - "443:443/udp"
    configs:
      - source: caddyfile
        target: /etc/frankenphp/Caddyfile
    volumes:
      - ./public:/app/public
      - ./test:/app/test
      - ./caddy_data:/data
      - ./caddy_config:/config

configs:
  caddyfile:
    content: |
      {
        frankenphp
      }

      example.com {
        root * /app/public
        encode zstd gzip
        php_server

        @private path /bl-content/databases/* /bl-content/pages/* /bl-content/tmp/* /bl-content/workspaces/*
        respond @private 404
      }

      www.example.com {
        redir https://example.com{uri} permanent
      }

      test.example.com {
        root * /app/test
        encode zstd gzip
        php_server
      }

Das war's schon — mehr Dateien braucht es nicht.

Was die einzelnen Blöcke tun

dockerfile_inline baut ein Mini-Image auf Basis von dunglas/frankenphp und installiert die PHP-Extension gd nach, die Bludit für Thumbnails braucht. Das offizielle Image bringt sie nicht mit. Wer Plugins nutzt, die mehr brauchen, hängt sie einfach an: install-php-extensions gd intl zip.

configs mit content schreibt das Caddyfile direkt aus der Compose-Datei in den Container. Der Vorteil: keine zweite Datei, die man vergessen kann. Der Nachteil: Änderungen greifen erst nach einem --force-recreate (dazu unten mehr).

php_server ist die Caddy-Direktive für „statische Dateien ausliefern, .php an PHP geben, alles andere auf index.php umschreiben". Genau das, was Bludits .htaccess unter Apache machen würde — die ist hier nämlich wirkungslos, weil kein Apache läuft. Bludits Friendly URLs funktionieren trotzdem.

Die Volumes. ./public ist die eigentliche Bludit-Installation, ./test eine zweite, unabhängige Instanz zum Ausprobieren. caddy_data enthält die TLS-Zertifikate und muss unbedingt persistent sein — sonst werden bei jedem Neustart neue geholt und irgendwann greifen Let's Encrypts Rate Limits.

Der @private-Matcher ist wichtig und wird gerne vergessen. Bludit legt Seiteninhalte unter bl-content/pages/DEIN-SLUG/index.txt ab. Ohne diese Regel kann jeder die Rohfassungen deiner Beiträge direkt abrufen — inklusive Entwürfen und geplanten Posts.

[!WARNING] Blockiere niemals /bl-kernel/*. Dort liegen auch CSS und JavaScript des Admin-Panels — das Backend wäre danach unbenutzbar.

Bludit installieren

Der Container liefert nur aus, was in ./public liegt. Bludit muss also noch dorthin.

1. Bludit herunterladen und entpacken

mkdir -p public test caddy_data caddy_config
cd public
curl -LO https://github.com/bludit/bludit/releases/download/3.22.0/bludit-3.22.0.zip
unzip bludit-3.22.0.zip && rm bludit-3.22.0.zip
cd ..

Achte darauf, dass index.php direkt in ./public liegt und nicht in einem Unterordner bludit/. Die aktuelle Version findest du immer auf bludit.com oder unter github.com/bludit/bludit/releases.

[!IMPORTANT] Bludit 3.22 setzt PHP 8.0 oder neuer voraus. Das aktuelle FrankenPHP-Image erfüllt das problemlos.

2. Container starten

docker compose up -d --build

Beim ersten Start baut Compose das Image und FrankenPHP fordert die Zertifikate an. Wenn etwas klemmt, verrät das Log warum:

docker compose logs -f php

3. Installer durchlaufen

Ruf https://example.com/ auf. Bludit zeigt den Installer, prüft die Voraussetzungen und fragt nach Sprache und Passwort. Danach ist das Backend unter https://example.com/admin erreichbar.

Der Installer legt beim Speichern die Verzeichnisse unter bl-content/ an. Der FrankenPHP-Container läuft standardmäßig als root, die Dateien gehören auf dem Host also root — für ein Single-Purpose-Setup unkritisch, beim Backup per unprivilegiertem User aber gut zu wissen.

4. Erste Einstellungen

  • Einstellungen → Allgemein: Website-URL ggf. manuell auf https://example.com setzen. Steht hier noch http:// oder eine falsche Domain, brechen Canonical-Links und Sitemap.
  • Einstellungen → Plugins: Prüfen, dass der Markdown Parser aktiviert ist, sofern du Beiträge in Markdown schreiben willst.
  • Die drei/vier Beispielinhalte löschen, die Bludit mitbringt.
  • Markdown Editor statt anderem

Themes/Plugins installieren

Themes sind bei Bludit einfach Ordner unter bl-themes/:

cd public/bl-themes
git clone https://github.com/Spleenftw/bludit-blowfish.git
cd ../../

Danach im Backend unter Einstellungen → Themes aktivieren. Dasselbe Prinzip gilt für Plugins in bl-plugins/; weitere gibt es auf plugins.bludit.com und themes.bludit.com.

Betrieb

Caddyfile ändern

Weil das Caddyfile als Compose-Config eingebettet ist, reicht ein restart nicht — der Container muss neu erzeugt werden:

docker compose up -d --force-recreate

Hast du am dockerfile_inline etwas geändert, zusätzlich --build anhängen.

Bludit aktualisieren

Neue Version herunterladen und alles außer bl-content/ überschreiben. bl-content/ enthält Inhalte, Uploads und die Konfiguration — der Ordner bleibt unangetastet. Details im Upgrade Guide.

Backup

Ein Tarball genügt, es gibt keine Datenbank zu dumpen:

tar czf bludit-$(date +%F).tar.gz public/bl-content

Wer ganz sichergehen will, sichert zusätzlich caddy_data/ — dann übersteht auch ein Umzug die Let's-Encrypt-Rate-Limits.

Erweiterung: weitere Dienste im selben Container

FrankenPHP bringt den vollen Caddy-Funktionsumfang mit. Es spricht also nichts dagegen, andere Dienste über dasselbe Caddyfile zu veröffentlichen:

panel.example.com {
  reverse_proxy https://10.0.0.5:8443 {
    transport http {
      tls_insecure_skip_verify
    }
  }
}

tls_insecure_skip_verify braucht man, wenn das Backend ein selbstsigniertes Zertifikat auf eine IP-Adresse ausstellt — typisch für Admin-Panels. Nach außen ist die Verbindung trotzdem sauber per Let's Encrypt verschlüsselt.

[!TIP] Läuft das Backend auf demselben Docker-Host, nimm lieber reverse_proxy dienst:port über ein gemeinsames Compose-Netzwerk, statt den Umweg über die öffentliche IP zu gehen.

Troubleshooting

Zertifikat wird nicht ausgestellt. Zeigt der DNS-Record wirklich auf diesen Server, und ist Port 80 von außen erreichbar? FrankenPHP braucht ihn für die HTTP-01-Challenge. Alternativ auf die DNS-01-Challenge umstellen.

Weiße Seite oder 404 bei Unterseiten. Fehlt php_server im Site-Block, oder ist root falsch gesetzt. Bludits .htaccess hilft hier nicht — den Rewrite übernimmt php_server.

Bilder werden hochgeladen, aber es entstehen keine Thumbnails. Die gd-Extension fehlt: docker compose exec php php -m | grep gd prüft das.

Änderungen am Caddyfile wirken nicht. --force-recreate vergessen.

Nützliche Links

Tech Bludit Caddy Docker
Share X (Twitter) Reddit LinkedIn