wallabag mit Docker Compose selbst hosten: Read-it-later mit Backup und Restore
Diese Anleitung zeigt wallabag mit Docker Compose, MariaDB und Redis: Installation, Passwortwechsel, lokale Bilder, OAuth-Client und API, Backup mit echtem Restore nach Totalverlust sowie das Update von 2.6.13 auf 2.6.14 inklusive der Fehler, die dabei wirklich auftreten.
Mit KI erstellt – redaktionelle Prüfung ausstehend
WerbelinksMit * markierte Links sind Werbelinks: Bei einem Kauf erhalten wir eine Provision, der Preis bleibt gleich. Als Amazon-Partner verdiene ich an qualifizierten Verkäufen. Mehr dazu

Ein Fachartikel, den Sie in Ruhe lesen wollen, ein Changelog für das nächste Wartungsfenster, eine Herstellerdoku, die morgen schon anders aussehen kann: Read-it-later-Dienste speichern solche Seiten als bereinigten Text. wallabag ist die quelloffene Variante dafür, MIT-lizenziert, mit Weboberfläche, Browser-Erweiterungen, Apps und einer REST-API. Die Inhalte liegen auf Ihrem eigenen Server statt bei einem Cloud-Anbieter. Diese Anleitung zeigt den Betrieb mit Docker Compose, MariaDB und Redis, das Speichern von Artikeln per API, ein Backup mit echtem Restore nach Totalverlust und das Update von 2.6.13 auf 2.6.14. Alle Ausgaben stammen aus einem eigenen Testlauf.
Voraussetzungen
wallabag ist eine Symfony-Anwendung mit PHP-FPM und Nginx im Container. Dazu kommen MariaDB und Redis. Für eine Instanz mit einigen Nutzern genügen:
- Linux-Server oder VM mit 1 bis 2 CPU-Kernen und 2 GB RAM, x86_64 oder ARM (das Image gibt es laut Docker Hub für amd64, arm64 und armv7)
- 5 GB freier Speicher für Images, Datenbank und gespeicherte Bilder, je nach Lesegewohnheit mehr
- Docker Engine mit Compose-Plugin v2
- eine eigene Subdomain mit Reverse Proxy und TLS, wenn Browser-Erweiterung und Apps von unterwegs zugreifen sollen
- ein Speicherziel außerhalb des Servers für die Backups
| Eckdaten | Wert |
|---|---|
| Image | wallabag/wallabag:2.6.14 (aktuelles Release vom 07.10.2025, identisch mit latest) |
| Port | 80/TCP im Container, im Beispiel auf 8080 veröffentlicht |
| Volumes | /var/www/wallabag/web/assets/images (Bilder), MariaDB unter /var/lib/mysql |
| Wichtige Env | SYMFONY__ENV__DOMAIN_NAME, SYMFONY__ENV__SECRET, SYMFONY__ENV__DATABASE_*, MYSQL_ROOT_PASSWORD |
| Standard-Login | wallabag / wallabag, sofort ändern |
Schritt 1: Projektordner, .env und compose.yaml anlegen
Die Vorlage folgt dem Compose-Beispiel aus dem offiziellen Repository wallabag/docker, ist aber an drei Stellen geschärft: feste Versions-Tags statt latest, Geheimnisse in der .env und ein Healthcheck auf /api/info. Ohne eigenes SYMFONY__ENV__SECRET nutzt das Image einen fest eingebauten Standardwert aus seiner Vorlage, den jeder kennt.
mkdir -p /opt/wallabag && cd /opt/wallabag
cat > .env <<EOF
WALLABAG_TAG=2.6.14
DOMAIN_NAME=https://wallabag.example.de
DB_ROOT_PASSWORD=$(openssl rand -hex 16)
DB_PASSWORD=$(openssl rand -hex 16)
APP_SECRET=$(openssl rand -hex 24)
EOF
chmod 600 .env
services:
wallabag:
image: wallabag/wallabag:${WALLABAG_TAG}
restart: unless-stopped
environment:
- MYSQL_ROOT_PASSWORD=${DB_ROOT_PASSWORD}
- SYMFONY__ENV__DATABASE_DRIVER=pdo_mysql
- SYMFONY__ENV__DATABASE_HOST=db
- SYMFONY__ENV__DATABASE_PORT=3306
- SYMFONY__ENV__DATABASE_NAME=wallabag
- SYMFONY__ENV__DATABASE_USER=wallabag
- SYMFONY__ENV__DATABASE_PASSWORD=${DB_PASSWORD}
- SYMFONY__ENV__DATABASE_CHARSET=utf8mb4
- SYMFONY__ENV__DATABASE_TABLE_PREFIX=wallabag_
- SYMFONY__ENV__SECRET=${APP_SECRET}
- SYMFONY__ENV__LOCALE=de
- SYMFONY__ENV__MAILER_DSN=smtp://127.0.0.1
- SYMFONY__ENV__FROM_EMAIL=wallabag@example.com
- SYMFONY__ENV__DOMAIN_NAME=${DOMAIN_NAME}
- SYMFONY__ENV__SERVER_NAME=wallabag
- SYMFONY__ENV__FOSUSER_REGISTRATION=false
ports:
- "8080:80"
volumes:
- ./images:/var/www/wallabag/web/assets/images
depends_on:
db:
condition: service_healthy
redis:
condition: service_healthy
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost/api/info"]
interval: 30s
timeout: 5s
start_period: 120s
db:
image: mariadb:11.8
restart: unless-stopped
environment:
- MARIADB_ROOT_PASSWORD=${DB_ROOT_PASSWORD}
volumes:
- ./db:/var/lib/mysql
healthcheck:
test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
interval: 20s
timeout: 5s
redis:
image: redis:8-alpine
restart: unless-stopped
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 20s
timeout: 3s
SYMFONY__ENV__DOMAIN_NAME muss die Adresse sein, unter der Nutzer wallabag aufrufen. Daraus baut die Anwendung unter anderem die URLs lokal gespeicherter Vorschaubilder. Läuft ein Reverse Proxy auf demselben Host, binden Sie den Port besser an 127.0.0.1:8080:80.
Verifizieren: docker compose config --quiet gibt nichts aus und endet mit Exit-Code 0.
Schritt 2: Starten, Bildordner berechtigen und Standardpasswort ändern
Legen Sie den Bildordner vor dem ersten Start an und übergeben Sie ihn dem Benutzer nobody, unter dem PHP-FPM im Container läuft. Legt Docker den Ordner selbst an, gehört er root. Im Test sah dann alles gesund aus, wallabag trug sogar lokale Bild-URLs in die Artikel ein, die Dateien fehlten aber. Ein Schreibversuch im Container zeigte den Grund: touch: /var/www/wallabag/web/assets/images/x: Permission denied.
mkdir -p images db
sudo chown 65534:65534 images
docker compose up -d
docker compose ps
curl -s localhost:8080/api/info
Beim ersten Start legt der Entrypoint mit dem Root-Passwort die Datenbank und den Benutzer wallabag an und installiert das Schema. Im Test war die Instanz rund 30 Sekunden nach dem Start healthy. Melden Sie sich mit wallabag / wallabag an und ändern Sie das Passwort sofort, entweder unter Konfiguration, Benutzer-Einstellungen oder in der Konsole:
docker compose exec wallabag su -s /bin/sh nobody -c \
"php bin/console fos:user:change-password wallabag 'NEUES-PASSWORT' --env=prod"
# Changed password for user wallabag
Die öffentliche Registrierung ist mit SYMFONY__ENV__FOSUSER_REGISTRATION=false abgeschaltet; /api/info meldet entsprechend "allowed_registration":false. Weitere Konten legen Sie als Admin unter Benutzerverwaltung oder mit fos:user:create an.
Verifizieren: docker compose ps zeigt alle drei Dienste als healthy, /api/info liefert {"appname":"wallabag","version":"2.6.14","allowed_registration":false}. Der Login mit dem alten Passwort wallabag führt zurück auf /login, der mit dem neuen auf die Startseite.
Schritt 3: Bilder lokal speichern
Standardmäßig verlinkt wallabag die Bilder eines Artikels nur auf der Originalseite. Verschwindet die Quelle, fehlen sie. Als Admin schalten Sie unter Interne Einstellungen im Reiter Verschiedenes den Wert Bilder lokal herunterladen auf 1. In der Datenbank heißt die Einstellung download_images_enabled, frisch installiert steht sie auf 0.

Gespeicherte Bilder landen unter images/ in Unterordnern nach Hash. Bestehende Artikel holen ihre Bilder nach, wenn Sie sie neu laden (Symbol Neu laden in der Artikelansicht oder API-Aufruf PATCH /api/entries/ID/reload.json).
Verifizieren: Speichern Sie einen Artikel mit Abbildungen; danach liefert find images -type f | wc -l einen Wert größer null. Im Test waren es nach drei Artikeln 19 Dateien.
Schritt 4: API-Client anlegen und Artikel per API speichern
Browser-Erweiterungen, Apps und eigene Skripte sprechen die API über OAuth2 an. Der naheliegende Konsolenbefehl fos:oauth-server:create-client bricht in 2.6.13 mit Too few arguments to function Wallabag\ApiBundle\Entity\Client::__construct(), 0 passed ab. Legen Sie den Client daher in der Weboberfläche unter API-Client-Verwaltung, Neuen Client erstellen an. Name genügt, die Weiterleitungs-URI bleibt für Skripte leer. Die Seite zeigt danach Client-ID und Client-Secret.

B=https://wallabag.example.de
curl -s -X POST $B/oauth/v2/token -d grant_type=password \
-d client_id=CLIENT_ID -d client_secret=CLIENT_SECRET \
-d username=wallabag --data-urlencode password='NEUES-PASSWORT'
# {"access_token":"...","expires_in":3600,"token_type":"bearer",...}
curl -s -H "Authorization: Bearer ACCESS_TOKEN" -X POST $B/api/entries.json \
--data-urlencode "url=https://www.debian.org/News/2025/20250809" -d tags=changelog
Ein Access-Token gilt eine Stunde, danach holen Sie mit dem Refresh-Token ein neues. Ein ungültiges Token beantwortet die API mit HTTP 401 und "error":"invalid_grant". Die Antwort auf den POST enthält Titel, Domain, Lesezeit und Tags des gespeicherten Artikels; in der Oberfläche erscheint er sofort unter Ungelesen.

Verifizieren: curl -s -H "Authorization: Bearer ACCESS_TOKEN" $B/api/entries.json nennt im Feld total die Zahl Ihrer Artikel, der neue Eintrag steht in _embedded.items.
Schritt 5: Backup von Datenbank, Bildern und Konfiguration
Die offizielle Doku nennt drei Bestandteile: Datenbank, Bilder unter web/assets/images und die Grundeinstellungen. Im Container entsteht parameters.yml bei jedem Start aus den Umgebungsvariablen, daher sichern Sie stattdessen .env und compose.yaml. Der Dump läuft im laufenden Betrieb konsistent mit --single-transaction:
cd /opt/wallabag && set -a && . ./.env && set +a
mkdir -p backup
docker compose exec -T db mariadb-dump -uroot -p"$DB_ROOT_PASSWORD" \
--single-transaction --routines --databases wallabag > backup/wallabag-db.sql
tar czf backup/wallabag-images.tgz images
cp .env compose.yaml backup/
Kopieren Sie den Ordner backup anschließend auf ein externes Ziel, etwa per restic oder rsync, und planen Sie den Ablauf per Cron. Der Dump enthält bewusst nur die Datenbank wallabag, nicht die MariaDB-Benutzerverwaltung. Das hat Folgen für den Restore, siehe Schritt 6.
Verifizieren: grep -c "INSERT INTO" backup/wallabag-db.sql liefert einen Wert größer null, tar tzf backup/wallabag-images.tgz | head listet Bilddateien. Im Test waren Dump und Bildarchiv bei drei Artikeln 68 KB und 40 KB groß.
Schritt 6: Restore nach Totalverlust
Im Test lief der Ernstfall vollständig: Marker-Tabelle angelegt, gesichert, Stack mit docker compose down -v entfernt, Datenbank- und Bildordner gelöscht. Nach dem Neustart der leeren Datenbank meldete MariaDB Unknown database 'wallabag'. Wer jetzt nur den Dump einspielt, bekommt eine Neustartschleife: wallabag erkennt die vorhandene Datenbank, überspringt mit WARN: MySQL database is already configured die Einrichtung samt Benutzer und scheitert dann an SQLSTATE[HY000] [1045] Access denied for user 'wallabag'. Der Benutzer gehört eben nicht zum Dump. Legen Sie ihn deshalb vor dem Import aus den Werten der .env an:
cd /opt/wallabag && set -a && . ./.env && set +a
docker compose down
sudo rm -rf db images && mkdir db
docker compose up -d db # warten, bis "healthy"
docker compose exec -T db mariadb -uroot -p"$DB_ROOT_PASSWORD" -e \
"CREATE USER IF NOT EXISTS 'wallabag'@'%' IDENTIFIED BY '$DB_PASSWORD';
GRANT ALL PRIVILEGES ON wallabag.* TO 'wallabag'@'%';"
docker compose exec -T db mariadb -uroot -p"$DB_ROOT_PASSWORD" < backup/wallabag-db.sql
sudo tar xzf backup/wallabag-images.tgz
docker compose up -d
Das Bildarchiv behält beim Entpacken als root die Eigentümer nobody:nogroup. Einen kompletten Dump der Benutzerverwaltung mit --system=users sollten Sie nicht einspielen: Im Test überschrieb er das Passwort des internen Benutzers healthcheck der neuen MariaDB-Instanz, der Container blieb danach mit Access denied for user 'healthcheck'@'localhost' dauerhaft unhealthy.

Verifizieren: Zehn Sekunden nach dem Start antwortete /api/info im Test wieder mit 200. Marker-Tabelle, alle drei Artikel samt Tags und alle 19 Bilder waren zurück, ein vorher ausgestelltes API-Token funktionierte weiter (HTTP 200), und ein lokal gespeichertes Bild lieferte über /assets/images/... HTTP 200.
Schritt 7: Update auf eine neue Version
Der Test startete bewusst mit 2.6.13 und zog die Instanz nach Backup und Restore auf 2.6.14. Sichern Sie vor jedem Update wie in Schritt 5, setzen Sie dann den Tag in der .env und starten Sie neu. Das Image führt Datenbankmigrationen nicht automatisch aus, rufen Sie sie daher nach dem Start auf:
sed -i 's/^WALLABAG_TAG=.*/WALLABAG_TAG=2.6.14/' .env
docker compose pull wallabag && docker compose up -d
docker compose exec wallabag su -s /bin/sh nobody -c \
"php bin/console doctrine:migrations:migrate --env=prod --no-interaction"
Der Container wurde neu erstellt, der Migrationsbefehl lief ohne Fehler durch. Artikel, Marker und API-Token blieben erhalten. Angemeldete Browser-Sitzungen waren danach beendet und leiteten auf /login um; Nutzer melden sich einfach neu an. Lesen Sie vor größeren Sprüngen die Upgrade-Hinweise in der offiziellen Doku.
Verifizieren: curl -s localhost:8080/api/info meldet "version":"2.6.14", docker compose ps zeigt das Image wallabag/wallabag:2.6.14 als healthy.
Troubleshooting
| Symptom | Ursache und Lösung |
|---|---|
| Artikel zeigen lokale Bild-URLs, die Bilder fehlen | Bildordner gehört root. chown 65534:65534 images, Artikel neu laden. |
Neustartschleife, Log zeigt [1045] Access denied for user 'wallabag' | Datenbank vorhanden, Benutzer fehlt oder Passwort weicht von der .env ab. Benutzer wie in Schritt 6 anlegen. |
fos:oauth-server:create-client endet mit Too few arguments | Konsolenbefehl scheiterte im Test mit 2.6.13. Client in der Weboberfläche anlegen. |
API antwortet 401 invalid_grant | Token abgelaufen (3600 Sekunden) oder falsch. Mit Refresh-Token oder Passwort neues Token holen. |
MariaDB nach Restore unhealthy, Access denied for user 'healthcheck' | Systembenutzer aus einem anderen Server eingespielt. Nur die Datenbank wallabag wiederherstellen. |
Log meldet bei jedem Start WARN: MySQL database is already configured | Hinweis, kein Fehler. Der Entrypoint empfiehlt, MYSQL_ROOT_PASSWORD nach der Ersteinrichtung aus dem wallabag-Dienst zu entfernen. |
Häufige Fragen
Reicht SQLite statt MariaDB?
Für eine Person ja. Ohne Datenbankvariablen nutzt das Image SQLite unter /var/www/wallabag/data; dann sichern Sie diesen Ordner im gestoppten Zustand. Für mehrere Nutzer und Importe größerer Bestände ist MariaDB robuster.
Wozu dient der Redis-Container?
Redis nimmt asynchrone Importaufgaben auf, etwa aus Pocket, Instapaper oder einem wallabag-Export. Den Worker starten Sie laut Image-Doku mit dem Befehl import <typ> in einem zusätzlichen Container mit denselben Variablen.
Kann ich wallabag direkt ins Internet stellen?
Nur hinter einem Reverse Proxy mit TLS, mit geändertem Standardpasswort und abgeschalteter Registrierung. Der Proxy muss X-Forwarded-Proto https setzen, und SYMFONY__ENV__DOMAIN_NAME muss die öffentliche HTTPS-Adresse enthalten.
Wie komme ich an meine Artikel, wenn ich wallabag aufgebe?
Über den Export in der Oberfläche (EPUB, PDF, JSON, CSV, TXT, XML) oder mit dem Konsolenbefehl wallabag:export. Der JSON-Export lässt sich in eine andere wallabag-Instanz importieren.
Fazit
wallabag ist schnell aufgesetzt und im Alltag unauffällig. Die Stolpersteine liegen nicht im Start, sondern im Betrieb: Bildordner mit falschem Eigentümer, ein defekter Konsolenbefehl für API-Clients und ein Restore, der ohne neu angelegten Datenbankbenutzer in einer Neustartschleife endet. Mit den drei Korrekturen aus dieser Anleitung überstand die Testinstanz Totalverlust und Update ohne Datenverlust. Für Teams, die Fachartikel, Doku-Stände und Changelogs dauerhaft und durchsuchbar ablegen wollen, ist das ein solider, schlanker Baustein.
Weiterführende Anleitungen und Quellen
- linkding mit Docker Compose: Lesezeichen-Manager fürs Team
- Karakeep mit Docker: Bookmark-Manager selbst hosten
- autorestic mit Docker Compose: Backups per restic
- wallabag auf GitHub
- Release-Notes wallabag 2.6.14
- Offizielles Docker-Repository wallabag/docker
- Image wallabag/wallabag auf Docker Hub
- wallabag-Doku: Backup
- wallabag-Doku: OAuth und API
- wallabag-Doku: Upgrade


